Skip to content

feat(export): portal export and web surface [roadmap:v0.11.0] - #77

Merged
tcballard merged 18 commits into
mainfrom
claude/lore-web-orchestration-mcixoc
Jun 12, 2026
Merged

feat(export): portal export and web surface [roadmap:v0.11.0]#77
tcballard merged 18 commits into
mainfrom
claude/lore-web-orchestration-mcixoc

Conversation

@tcballard

@tcballard tcballard commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements rac/roadmaps/v0.11.x-portal/v0.11.0-portal-export.md.

Adds:

  • rac export — a deterministic JSON corpus payload (default mode) and, with --html, the Lore Portal: one self-contained HTML file of the whole corpus that opens from file:// with zero network requests.
  • The reconciled viewer contract (lore-web/VIEWER_CONTRACT.md, v1): the lore-web viewer now consumes exactly what Core emits, and the built viewer shell is vendored into the Python package with recorded provenance and a drift-guard test.
  • The lore-web web surface this branch developed en route: design system + primitives, a prerendered landing page (open-source framing, real recorded demo, real mascot), and a build-to-artifact CI workflow with no publish step.
  • 22 export tests (golden, round-trip, determinism, boundaries, drift guard), README usage documentation, changelog entry, and the registry manifest bump to 0.11.0.

Roadmap / ADR Trace

Roadmap:

  • rac/roadmaps/v0.11.x-portal/v0.11.0-portal-export.md

Relevant ADRs:

  • rac/decisions/adr-012-open-core-strategy.md — import/export are open core
  • rac/decisions/adr-014-viewer-agnostic-knowledge-artifacts.md — Core exports data; viewers are interchangeable consumers
  • rac/decisions/adr-007-json-contract-stability.md — the payload is a public, golden-tested contract
  • rac/decisions/adr-002-ai-optional.md, adr-032 — offline, stateless, deterministic
  • rac/decisions/adr-029-guide-delivery-surface.md, adr-031 — ships as a rac subcommand, consumes Core in-process, read-only

Scope

Included

  • rac export [directory] [--json | --html] [--out PATH]: service (rac.services.export), output layer (rac.output.portal, render_export_json), CLI wiring, package data for the vendored shell.
  • Payload v1: string schema_version; corpus with name, rac_version, artifact_count; artifacts with id, aliases, type, status, title, path, body_html in path order; relationships as relates-to edges sorted by (from, to) with unresolved references preserved verbatim.
  • Viewer reconciliation: alias-preferred display and citation linkification, case-insensitive status handling, provenance line, sample corpus regenerated to v1.
  • Shell vendoring with provenance.json (lore-web commit, shell hash, normative viewer source-tree hash) and a Python drift-guard test that re-implements the hash and instructs re-vendoring on divergence.
  • Web surface: token design system (lantern amber, contrast-verified at 4.5:1 or better), six primitives, landing page (prerendered at build time, Lighthouse 99/100 performance, 100 accessibility), export viewer shell, lore-web-build.yml CI (artifact upload only).

Excluded

  • No graph visualisation in the Portal (explicitly deferred by the viewer contract).
  • No supersedes/refines/implements edge typing — Core's relationships are family-grouped but semantically untyped; richer types are a future Core decision, not an export invention. The viewer accepts an open set, so the schema is forward-compatible.
  • No generated_at timestamp in the payload — would break byte-determinism; revisit with an explicit flag if provenance-in-payload is ever needed.
  • No hosted or multi-tenant viewing; the trust boundary is the repository. body_html is rendered by Core with raw HTML escaped and the viewer renders it as-is.
  • No deployment of the lore-web site: CI builds to an artifact only.
  • No changes to the Guide tool surface.

Product / Architecture Decisions

  • The Portal ships as vendored package data, not a Python-native renderer: Core produces data; the shell is an inert byte string with one substitution seam (an empty JSON data element with id lore-export), keeping presentation out of Core per ADR-014 while reusing the verified viewer. Drift risk is covered by the provenance hash test.
  • markdown-it-py's commonmark preset enables raw HTML (CommonMark spec behaviour), contrary to the roadmap's assumption — the implementation disables HTML explicitly, and a test pins that a raw script tag in a source body arrives entity-escaped in body_html.
  • JSON is the default CLI mode (the payload is the product); --html overwrites an existing --out because exports are build artifacts (stated in --help), unlike rac new which never overwrites.
  • status falls back to "unknown" and title to the canonical id where absent — the viewer contract pins strings; rac inspect semantics are otherwise reused (canonical_value against the spec).
  • Unknown-type files are skipped (and not counted in artifact_count), mirroring rac index; classification stays separate from validation, so invalid-but-recognizable artifacts export as classified.
  • The injected payload escapes the close-tag and comment-open character sequences using standard JSON string escapes, so the embedded document parses unchanged while staying safe inside its host element.
  • Naming: the MCP server is the Guide; the exported HTML viewer is the Portal. The v0.11.x roadmap series starts here.

User-Facing Contract

CLI

rac export rac/                                  # canonical JSON to stdout
rac export rac/ --html --out lore-export.html    # the Portal, one file

Human Output

  • --html prints exactly one confirmation line: wrote lore-export.html — 121 artifact(s), 323 relationship(s).

JSON Output

{
  "schema_version": "string — \"1\", matching the index contract",
  "corpus": "name (directory basename), rac_version, artifact_count",
  "artifacts": "id, aliases (canonical first), type, status, title, path, body_html",
  "relationships": "from, to (resolved id or literal reference), type (relates-to)"
}

Exit Codes

  • 0: export written/emitted (including an empty corpus)
  • 2: usage/IO error — missing directory, unwritable --out, --out without --html, missing or corrupt vendored shell seam

Verification

Ran

pytest                                   # 1062 passed (post-merge with v0.10.6)
ruff check src/ tests/ && ruff format --check src/ tests/ && mypy src/
rac validate rac/                        # PASS, 121 valid
rac relationships rac/ --validate        # 0 issues
rac review rac/                          # no priority 1-2 findings
rac export rac/ && rac export rac/ --html --out /tmp/portal.html

Covered

  • Golden JSON against a fixture corpus (version monkeypatched for byte-stability); two runs byte-identical.
  • HTML round-trip: embedded payload equals --json output; shell byte-identical around the seam; payload script-safe (close-tag and comment-open sequences escaped).
  • Boundaries: missing directory exits 2; empty corpus yields a valid empty payload; unresolved reference preserved verbatim; raw HTML in a source body arrives escaped.
  • Drift guard matches provenance.json and skips outside the repository checkout.
  • Headless Chromium opened the exported Portal of this repository from file://: zero console errors, zero network requests, filters and citation cross-links working.

Review Path

  1. src/rac/services/export.py — the payload model and composition of existing Core services
  2. src/rac/output/portal.py, src/rac/output/json.py — seam injection and JSON rendering
  3. src/rac/cli.py — command wiring and exit codes
  4. tests/test_export_cmd.py, tests/fixtures/export/, tests/golden/export_json.txt
  5. lore-web/VIEWER_CONTRACT.md, lore-web/src/viewer/ — the reconciled consumer
  6. src/rac/templates/portal/ — vendored shell + provenance (generated; review the manifest, skim the shell)
  7. README.md, CHANGELOG.md, server.json, rac/roadmaps/v0.11.x-portal/

Notes For Reviewer

  • src/rac/templates/portal/lore-portal-shell.html is a generated artifact (~950 KB; source of truth in lore-web/); regenerate with cd lore-web && npm run vendor:shell.
  • The branch also carries the lore-web landing-page work (design system, prerendered landing, recorded demo). It is inert from the package's perspective — nothing outside lore-web/ and the CI artifact workflow references it — but it is in the diff.
  • main's v0.10.6 (anonymous usage sharing) is merged in; conflicts were limited to the CHANGELOG and the cli.py exit-code docstring, resolved by keeping both entries.
  • Tagging v0.11.0 after merge completes the release; the package version is tag-derived (setuptools-scm) and server.json is already bumped.
  • Deferred follow-ups recorded in the roadmap: graph visualisation, typed edges, payload timestamps.

Implementation Process

Implemented with AI assistance under the roadmap contract.

Final scope, review, and acceptance decisions were made by the maintainer.

tcballard added 18 commits June 12, 2026 18:46
Stages the visual spec for the Lore web surface at
lore-web/design/mockup-landing.png. The mockup is authoritative for
look and feel of the landing page and export viewer shell.
Scaffolds the static Lore web surface (Vite + React + TS, multi-page,
no server code) and ships the design system: token sheet (lantern-amber
ruling, three surfaces, dashed-container chrome), six primitives
(Panel, TerminalFrame, Prompt, CheckItem, KeyboardHint, CommandPalette)
with a demo page, a programmatic WCAG contrast report (all 24 used
text/surface pairs pass 4.5:1; --error lightened #cb6f6f -> #ce7878),
self-hosted JetBrains Mono with OFL licence, a generated pixel-lantern
placeholder, and DESIGN.md recording the five aesthetic rules.
Implements the landing mockup as a static single page: hero with
placeholder lantern, differentiators, real next-step navigation, a
clearly labelled placeholder slot for the (pending) real demo
recording, mailto beta signup, MCP-tools rail, the why-panel with a
copyable install command, and the footer command palette wired as a
page navigator (not a chatbot). Landing JS is ~49.8 KB gzipped against
the 150 KB budget; copy verified against the product context with
aspirational lines caveated.
Adds the read-only viewer that 'lore export --html' will emit: artifact
list with type/status filters and debounced client-side search,
artifact detail with cited-ID cross-links, and a typed-edge related
panel (list form; graph visualisation deferred). A dedicated
single-entry build inlines JS/CSS and embeds the corpus JSON so the
emitted lore-viewer.html opens from file:// with zero network requests
(verified in headless Chromium; 58.3 KB gzipped with the sample corpus,
500-artifact corpus filters at ~4 ms/keystroke). VIEWER_CONTRACT.md
documents the lore-export.json schema as a proposal to reconcile with
Lore Core. Ships a clearly labelled 30-artifact sample corpus for a
fictional Python billing service.
Builds the static site and the single-file viewer on lore-web pull
requests, runs the contrast report and the no-hex-outside-tokens gate,
and uploads dist/ as a CI artifact. Deliberately contains no publish or
deploy step (GATE-1: no public deployment until external-communications
policy review completes).
Adds the lamplighter mascot (background keyed out, palette-quantised to
16 KB display asset plus full-resolution source and favicon) and a real
terminal recording: asciinema capture of pip install, claude mcp add
lore -- rac mcp, rac find, rac resolve, and rac validate run against
this repository's own corpus, rendered to an animated SVG remapped to
the token palette (16 KB gzipped, no JS). scripts/record-demo.sh
documents the full reproduction pipeline; design/demo.cast is the
source capture. Nothing in the recording is mocked.
Removes all closed-beta framing: the beta signup section and mailto are
gone, replaced by a Get Lore section with the two real commands
(pip install requirements-as-code; claude mcp add lore -- rac mcp) as
copyable code, a note that Lore ships on the open-source RAC engine
under the rac name, and a link to the GitHub repository. Corrects the
previously wrong install command (-- lore mcp) and the rail's CI line
to rac validate, both verified by running them. The hero now shows the
real mascot, and the demo placeholder is replaced by the recorded
session (IntersectionObserver-gated so the animation does not tax page
load, with a noscript fallback).
Prerenders the landing at build time: a Vite SSR pass renders
LandingApp to static HTML injected into dist/index.html (stylesheets
inlined, hero image preloaded), and main.tsx hydrates when server
markup is present. Subsets JetBrains Mono to the character ranges the
site uses (187 KB to 78 KB of woff2). With the quantised mascot this
takes Lighthouse mobile from performance 84 / LCP 2.9 s to
performance 99 / LCP 1.8 s, with FCP 1.7 s, TBT 0 ms, CLS 0; content
is now readable without JavaScript.
Starts the v0.11.x portal series: rac export emits a deterministic
JSON corpus payload (stable public contract per ADR-007) and, with
--html, a single self-contained Portal file built by injecting the
payload into a viewer shell vendored from lore-web. Pins the payload
shape, module locations, CLI surface, exit codes, vendoring provenance
with a drift guard, and the test battery.

Implements rac/roadmaps/v0.11.x-portal/v0.11.0-portal-export.md.
… [roadmap:v0.11.0]

Reconciles the lore-web viewer to the payload rac export will emit:
string schema_version, corpus {name, rac_version, artifact_count},
artifacts with aliases and path (alias-preferred display, provenance
line), case-insensitive status handling, uniform relates-to edges with
unresolved targets preserved. Adds a --shell-only build mode and a
vendoring script that writes the built shell plus a provenance manifest
(lore-web commit, shell hash, normative viewer source-tree hash) into
src/rac/templates/portal/ for the Python drift guard. Sample corpus
regenerated to v1; VIEWER_CONTRACT.md graduates from proposal to
reconciled v1. Verified headlessly from file:// with zero network
requests.

Implements rac/roadmaps/v0.11.x-portal/v0.11.0-portal-export.md
(Initiative 2).
….11.0]

build_corpus_export composes one corpus walk with Core identity and
the relationship alias index into a typed CorpusExport: artifacts
(id, aliases, type, status, title, path, body_html) in path order and
relates-to edges sorted by (from, to), unresolved references preserved
verbatim. Bodies render via markdown-it-py CommonMark with raw HTML
explicitly disabled, so source HTML arrives escaped. The payload is
deterministic — no timestamps, stable ordering — and render_export_json
emits it under the ADR-007 contract.

Implements rac/roadmaps/v0.11.x-portal/v0.11.0-portal-export.md
(Initiative 1).
…v0.11.0]

rac export [directory] [--json | --html] [--out PATH]: JSON is the
default mode and prints to stdout; --html injects the payload into the
vendored portal shell's data seam (escaping </ and <!-- inside the
JSON) and writes one self-contained file, overwriting prior exports and
confirming with a single line. Usage and IO failures, including a
missing or corrupt shell seam, exit 2. Portal template files join the
wheel via package data.

Implements rac/roadmaps/v0.11.x-portal/v0.11.0-portal-export.md
(Initiative 3).
…:v0.11.0]

22 tests in the new export battery: golden JSON against a fixture
corpus (version monkeypatched for byte-stability), HTML round-trip
proving the embedded payload equals --json with the shell byte-identical
around the seam, determinism across runs, boundary cases (missing
directory exits 2, empty corpus, unresolved reference preserved, raw
HTML escaped), and the drift guard that re-implements the normative
viewer source-tree hash and fails with re-vendoring instructions when
lore-web diverges from the vendored shell.

Implements rac/roadmaps/v0.11.x-portal/v0.11.0-portal-export.md.
Adds the Sharing the corpus section: rac export for the canonical JSON
contract and --html for the single-file Portal.
Adds the v0.11.0 changelog entry and bumps the MCP registry manifest
to 0.11.0. Minor release gate reviewed: no artifact-specific
classification or duplicated validation/schema behaviour (export is
spec-driven and reuses the relationship alias index); no new
artifact-type conditionals; unknown-type skipping, JSON golden, HTML
round-trip, and output contracts are all pinned; full pytest run green
(1025 passed) before commit.
…stration-mcixoc

# Conflicts:
#	CHANGELOG.md
#	src/rac/cli.py
…v0.11.0]

Opens the discussion on citation rendering across surfaces: alias-first
display with title context, surface-appropriate link targets (repo path
in editors, hash route in the Portal, absolute URLs only behind
explicit configuration), state-aware styling for superseded targets,
and additive-only changes to pinned Guide contracts. Records the open
questions — where a Portal canonically lives, hover affordance depth,
chat-client link rendering — as the agenda. Proposed status; nothing
scheduled.

Implements nothing yet; seeds a future v0.11.x item.
@tcballard
tcballard merged commit 38ae6d8 into main Jun 12, 2026
3 checks passed
@tcballard
tcballard deleted the claude/lore-web-orchestration-mcixoc branch June 15, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant