Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ morphir-python - FINOS
Copyright 2026 - FINOS - info@finos.org

This product includes software developed at the Fintech Open Source Foundation (https://www.finos.org/).

The documentation theme adapts the FINOS morphir-moonbit site design
from revision bd3adc52e6b8f6368fdaee50f76535799732f8d6, under Apache-2.0.
See site/theme/README.md for source attribution.
19 changes: 19 additions & 0 deletions docs/site-foundation.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,22 @@ Telescope 0.2.1 treats existing files outside discovered roots as missing.
minimal reproduction and requested source-link contract. Explicit GitHub URLs
are the current non-blocking workaround; internal published-page links remain
relative and checked.

## Morphir theme and landing page

The Python site uses the blue/orange palette and responsive landing-page design
from [morphir-moonbit at bd3adc52](https://github.com/finos/morphir-moonbit/blob/bd3adc52e6b8f6368fdaee50f76535799732f8d6/site/pages/index.html).
The attributed consumer theme lives in `site/theme/morphir.css`; its README
records the source revision and digest. The landing model matches the checked
`examples/model/acme/models.py` source. Browser checks cover both themes, narrow
and desktop screens, root and project bases, guide navigation and exact colors.

Telescope 0.2.1 has no consumer stylesheet hook. Replacing its reserved
`assets/site.css` through static resources fails with a duplicate-output error.
[Telescope issue 13](https://github.com/moonrockz/telescope/issues/13) records the
reproduction and requested reusable extension. The current consumer build stages
stock output, appends the theme to its CSS, runs the stock checker on those final
bytes, then atomically promotes the checked tree. A failed stylesheet check
preserves the previous output. The site publisher seals those final bytes, so
there is no styling transformation during deployment. Adopt a published upstream
stylesheet hook when qualified, replacing this bounded workaround.
5 changes: 4 additions & 1 deletion openspec/changes/add-documentation-site/design.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<body>
<main>
<h1>add-documentation-site</h1>
<p class="meta">OpenSpec change · Bead <code>morphir-ukvn.9</code> · 2026-10-28 · Implemented, awaiting review</p>
<p class="meta">OpenSpec change · Bead <code>morphir-ukvn.9</code> · 2026-10-28 · Published foundation; MoonBit visual alignment in progress</p>

<nav class="toc" aria-label="Contents">
<ul>
Expand Down Expand Up @@ -76,6 +76,9 @@ <h3 id="site-1">SITE-1. Consume the published Telescope CLI</h3>
</code></pre>
<p>This is a configuration starting point, not the final navigation manifest. Configure exclusions, navigation and static resources using the pinned version's supported contract. Build to <code>_site/</code>; test both <code>/</code> and <code>/morphir-python/</code>. Keep existing guide URLs in the repository useful and add site links to READMEs. Copying MoonBit's renderer would duplicate work now owned by Telescope; a separate Python documentation framework would split that shared foundation.</p>

<h3 id="site-2">SITE-2. Reuse the Morphir MoonBit visual design</h3>
<p>Use the blue/orange palette and responsive hero, code window, pipeline and feature cards from morphir-moonbit bd3adc52, adapting only the Python content and guide links. Keep the attributed CSS in site/theme/morphir.css and bridge Telescope shell variables to the same palette. The hero model is the checked examples/model/acme/models.py source; browser checks compare the displayed text and exercise both themes, viewports and deployment bases.</p>
<p>Telescope 0.2.1 has no consumer stylesheet hook and rejects a static assets/site.css collision. Append the consumer theme to stock generated CSS in a private build directory, run Telescope checks on those final bytes, then atomically promote the checked tree. A broken CSS reference must leave the previous publication unchanged. Report this reusable gap upstream and retain the published renderer rather than fork it. This workaround changes presentation, not the renderer or browser controls.</p>
<h3 id="docs-1">DOCS-1. Organize around complete user journeys</h3>
<table>
<thead><tr><th>Navigation</th><th>Initial content</th></tr></thead>
Expand Down
2 changes: 1 addition & 1 deletion openspec/changes/add-documentation-site/proposal.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Python users need a discoverable guide to the library, compiler API, CLI and ext

## What Changes

- Publish a repository-owned documentation site using a pinned release of moonrockz/telescope, with navigation, search, readable code examples and local preview instructions informed by morphir-moonbit's site.
- Publish a repository-owned documentation site using a pinned release of moonrockz/telescope, with navigation, search, readable code examples and local preview instructions. Use morphir-moonbit's blue/orange palette and responsive hero, code window, pipeline and feature-card landing design.
- Add tested examples for Python API compilation/generation, IR codecs and migration, SDK usage, and CLI compile/generate/inspect/convert workflows. Explain package installation, supported platforms, diagnostics and language limits.
- Document the Python extension's qualified installation and invocation through finos/morphir. Publish a compatibility matrix and verified file-based IR interchange recipes for the MoonBit-based CLI, with explicit unsupported cases.
- Keep MoonBit Extism adapter implementation in a separate upstream proposal. This change documents current support and links the follow-up; it does not claim that the existing Python WASM bundle works with the MoonBit core-WASM adapter.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,10 @@ When a site-foundation limitation obstructs a required experience, contributors
#### Scenario: A required presentation feature is unavailable
- **WHEN** a contributor confirms the pinned foundation cannot render a required example interaction
- **THEN** the change records an upstream report or existing proposal and labels any workaround and remaining acceptance gap

### Requirement: The site follows the Morphir MoonBit visual design
The documentation SHALL use morphir-moonbit's blue/orange palette in light and dark themes, and its landing-page structure with a split hero, code window, workflow pipeline and feature cards. Content and calls to action SHALL describe qualified Python workflows and remain usable at 360 pixels. The displayed model MUST match a canonical checked Python example. (bead: morphir-ukvn.9.1)

#### Scenario: Open the Python landing page
- **WHEN** a reader opens the site at either supported deployment base in either theme
- **THEN** the Morphir palette and responsive landing design are visible, the model matches the checked source, and the main actions reach the API and CLI guides without overflow
2 changes: 2 additions & 0 deletions openspec/changes/add-documentation-site/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Implement the contracts in `specs/docs/` using the decisions in `design.html`. S
- [x] 1.4 Add browser smoke coverage for desktop and 360px, light/dark themes, keyboard focus, navigation, search and code copying; verify no page-level overflow and retain screenshots/results for the candidate.
- [x] 1.5 Record the foundation/version/limitations in `docs/site-foundation.md`; link Telescope's existing tutorial intent when durable, and report any confirmed reusable blocker upstream with reproduction and acceptance criteria. Verify each remaining gap has a linked report or an explicit non-blocking consumer workaround.

- [ ] 1.6 Match morphir-moonbit revision bd3adc52 colors and landing design using an attributed theme and Python example; verify both themes, desktop/mobile layouts, canonical source, base paths and atomic failure preservation, report the Telescope customization gap upstream and verify the published site after merge.

## 2. Publish verified Python API and CLI examples (morphir-ukvn.9.2)

- [x] 2.1 Add canonical multi-module model, compiler API and CLI recipe sources under `examples/`, with a prerequisite/result manifest; verify compile/generate/recompile semantic assertions and diagnostic/output-safety cases against installed packages outside the checkout.
Expand Down
33 changes: 33 additions & 0 deletions scripts/site_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,37 @@ def setup() -> Path:
)


def build_site(binary: Path, base: str, out: str, *, root: Path = ROOT) -> None:
"""Check Telescope output and the consumer theme before atomic promotion."""
root = root.resolve()
destination = (root / out).resolve()
if destination == root or not destination.is_relative_to(root):
raise SiteError("Site output must be a directory inside the project")
destination.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(
prefix=f".{destination.name}-", dir=destination.parent
) as scratch:
staged = Path(scratch) / "site"
arguments = ["--config", "site/site.json", "--out", str(staged), "--base", base]
subprocess.run([str(binary), "build", *arguments], cwd=root, check=True)
# Telescope has no consumer stylesheet hook. Preserve its shell and
# append our attributed theme before checking and sealing final bytes.
stylesheet = staged / "assets/site.css"
with stylesheet.open("ab") as stream:
stream.write(b"\n/* Morphir consumer theme */\n")
stream.write((root / "site/theme/morphir.css").read_bytes())
subprocess.run([str(binary), "check", *arguments], cwd=root, check=True)
previous = Path(scratch) / "previous"
if destination.exists():
destination.rename(previous)
try:
staged.rename(destination)
except OSError:
if previous.exists():
previous.rename(destination)
raise


def main(argv: list[str] | None = None) -> None:
"""Run a reproducible site command without installing the Rust toolchain."""
parser = argparse.ArgumentParser(description=__doc__)
Expand All @@ -129,6 +160,8 @@ def main(argv: list[str] | None = None) -> None:
[sys.executable, str(ROOT / "scripts/docs_examples.py"), "archive"],
check=True,
)
build_site(binary, args.base, args.out)
return
command = [
str(binary),
args.command,
Expand Down
26 changes: 26 additions & 0 deletions site/browser/site.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
import { test, expect } from '@playwright/test';
import { fileURLToPath } from 'node:url';
import { readFile } from 'node:fs/promises';

for (const width of [1440, 360]) for (const theme of ['light', 'dark']) {
test(`MoonBit design at ${width}px in ${theme}`, async ({ page }, info) => {
const base = info.project.metadata.base;
await page.setViewportSize({ width, height: 900 });
await page.emulateMedia({ colorScheme: theme });
await page.goto(base);
await expect(page.getByRole('heading', { name: 'Morphir, in Python', exact: true })).toBeVisible();
await expect(page.locator('.hero .code-window')).toBeVisible();
await expect(page.locator('.code-window pre')).toHaveCSS('background-color', 'rgb(15, 20, 27)');
const model = await readFile(new URL('../../examples/model/acme/models.py', import.meta.url), 'utf8');
expect((await page.locator('.code-window pre code').textContent()).trimEnd()).toBe(model.trimEnd());
await expect(page.locator('.pipeline li')).toHaveCount(3);
await expect(page.locator('.features .feature')).toHaveCount(3);
await expect(page.locator('.button.primary')).toHaveCSS('background-color', 'rgb(255, 106, 0)');
await expect(page.locator('body')).toHaveCSS('background-color', theme === 'dark' ? 'rgb(15, 20, 27)' : 'rgb(255, 255, 255)');
const metrics = await page.evaluate(() => ({ width: document.documentElement.scrollWidth, viewport: innerWidth, columns: getComputedStyle(document.querySelector('.hero')).gridTemplateColumns.split(' ').length }));
expect(metrics.width).toBeLessThanOrEqual(metrics.viewport + 1);
expect(metrics.columns).toBe(width === 360 ? 1 : 2);
await page.screenshot({ path: fileURLToPath(new URL(`../../.dev/site-browser/${info.project.name}-home-${width}-${theme}.png`, import.meta.url)), fullPage: true });
await page.getByRole('link', { name: 'Start with Python', exact: true }).click();
await expect(page.getByRole('heading', { name: 'A Python model round trip', exact: true })).toBeVisible();
await expect(page.locator('body')).toHaveCSS('background-color', theme === 'dark' ? 'rgb(15, 20, 27)' : 'rgb(255, 255, 255)');
});
}

for (const width of [1440, 360]) for (const theme of ['light', 'dark']) {
test(`read and navigate at ${width}px in ${theme}`, async ({ page }, info) => {
Expand Down
55 changes: 55 additions & 0 deletions site/pages/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!-- ---
title: Morphir Python
--- -->
<section class="hero">
<div class="hero-text">
<h1>Morphir, in <span class="hero-accent">Python</span></h1>
<p class="lede">Write domain models in Python, exchange Morphir IR and generate Python libraries.
Use the same compiler through the Python API, CLI or Morphir extension.</p>
<p class="actions">
<a class="button primary" href="/docs/quickstart/">Start with Python</a>
<a class="button" href="/docs/python-compiler/">Explore the API</a>
</p>
</div>
<figure class="code-window" aria-label="A Python domain model">
<figcaption><span class="code-dot" aria-hidden="true"></span>models.py <span class="code-note">compiles to Morphir IR</span></figcaption>
<pre><code class="language-python"><span class="tok-keyword">from</span> __future__ <span class="tok-keyword">import</span> annotations

<span class="tok-keyword">from</span> dataclasses <span class="tok-keyword">import</span> dataclass


@dataclass(frozen=<span class="tok-keyword">True</span>)
<span class="tok-keyword">class</span> <span class="tok-name">Pending</span>:
<span class="tok-keyword">pass</span>


@dataclass(frozen=<span class="tok-keyword">True</span>)
<span class="tok-keyword">class</span> <span class="tok-name">Approved</span>:
amount: <span class="tok-name">float</span>


<span class="tok-keyword">type</span> Decision = Pending | Approved</code></pre>
</figure>
</section>
<ol class="pipeline" aria-label="How Morphir Python works">
<li class="step-source">Python domain model</li>
<li class="step-ir">Morphir IR</li>
<li class="step-target">Generated Python library</li>
</ol>
<section class="features" aria-label="Work with Morphir Python">
<article class="feature feature-blue">
<span class="feature-icon icon-ir" aria-hidden="true"></span>
<h2>Work with Morphir IR</h2>
<p>Read, write and migrate versioned models with the dependency-free <a href="/docs/library/">Python library</a>.</p>
</article>
<article class="feature feature-orange">
<span class="feature-icon icon-run" aria-hidden="true"></span>
<h2>Compile through Python or the CLI</h2>
<p>Compile and generate in memory with the <a href="/docs/python-compiler/">typed API</a>, or follow a complete <a href="/docs/quickstart/">CLI round trip</a>.</p>
</article>
<article class="feature feature-blue">
<span class="feature-icon icon-code" aria-hidden="true"></span>
<h2>Connect to Morphir</h2>
<p>Install the extension with <a href="/docs/python-extension/">finos/morphir</a>. Check the <a href="/docs/compatibility/">qualified host matrix</a> for MoonBit integration and IR interchange.</p>
</article>
</section>
32 changes: 0 additions & 32 deletions site/pages/index.md

This file was deleted.

11 changes: 11 additions & 0 deletions site/theme/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Morphir site theme

Adapted from `apps/morphir-site/assets/site.css` in FINOS
[morphir-moonbit](https://github.com/finos/morphir-moonbit/blob/bd3adc52e6b8f6368fdaee50f76535799732f8d6/apps/morphir-site/assets/site.css),
revision `bd3adc52e6b8f6368fdaee50f76535799732f8d6`, under Apache-2.0.
Source SHA-256: `9ade4dbd64887a5c04b4832dc59a1338cb589595a693d6970f46ba28665187ff`.

The blue/orange palette, responsive hero, code window, pipeline and feature
cards follow that reference. The final aliases connect Telescope shell tokens
to the same palette. The renderer and browser controls remain stock Telescope.
The build appends this stylesheet before validation and atomic promotion.
Loading
Loading