feat(export): portal export and web surface [roadmap:v0.11.0] - #77
Merged
Conversation
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.
This was referenced Jun 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 fromfile://with zero network requests.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.Roadmap / ADR Trace
Roadmap:
rac/roadmaps/v0.11.x-portal/v0.11.0-portal-export.mdRelevant ADRs:
rac/decisions/adr-012-open-core-strategy.md— import/export are open corerac/decisions/adr-014-viewer-agnostic-knowledge-artifacts.md— Core exports data; viewers are interchangeable consumersrac/decisions/adr-007-json-contract-stability.md— the payload is a public, golden-tested contractrac/decisions/adr-002-ai-optional.md,adr-032— offline, stateless, deterministicrac/decisions/adr-029-guide-delivery-surface.md,adr-031— ships as aracsubcommand, consumes Core in-process, read-onlyScope
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.schema_version;corpuswith name, rac_version, artifact_count; artifacts with id, aliases, type, status, title, path, body_html in path order; relationships asrelates-toedges sorted by (from, to) with unresolved references preserved verbatim.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.lore-web-build.ymlCI (artifact upload only).Excluded
generated_attimestamp in the payload — would break byte-determinism; revisit with an explicit flag if provenance-in-payload is ever needed.body_htmlis rendered by Core with raw HTML escaped and the viewer renders it as-is.Product / Architecture Decisions
lore-export), keeping presentation out of Core per ADR-014 while reusing the verified viewer. Drift risk is covered by the provenance hash test.body_html.--htmloverwrites an existing--outbecause exports are build artifacts (stated in--help), unlikerac newwhich never overwrites.statusfalls back to"unknown"andtitleto the canonical id where absent — the viewer contract pins strings;rac inspectsemantics are otherwise reused (canonical_valueagainst the spec).artifact_count), mirroringrac index; classification stays separate from validation, so invalid-but-recognizable artifacts export as classified.User-Facing Contract
CLI
Human Output
--htmlprints 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,--outwithout--html, missing or corrupt vendored shell seamVerification
Ran
Covered
--jsonoutput; shell byte-identical around the seam; payload script-safe (close-tag and comment-open sequences escaped).provenance.jsonand skips outside the repository checkout.file://: zero console errors, zero network requests, filters and citation cross-links working.Review Path
src/rac/services/export.py— the payload model and composition of existing Core servicessrc/rac/output/portal.py,src/rac/output/json.py— seam injection and JSON renderingsrc/rac/cli.py— command wiring and exit codestests/test_export_cmd.py,tests/fixtures/export/,tests/golden/export_json.txtlore-web/VIEWER_CONTRACT.md,lore-web/src/viewer/— the reconciled consumersrc/rac/templates/portal/— vendored shell + provenance (generated; review the manifest, skim the shell)README.md,CHANGELOG.md,server.json,rac/roadmaps/v0.11.x-portal/Notes For Reviewer
src/rac/templates/portal/lore-portal-shell.htmlis a generated artifact (~950 KB; source of truth inlore-web/); regenerate withcd lore-web && npm run vendor:shell.lore-web/and the CI artifact workflow references it — but it is in the diff.v0.11.0after merge completes the release; the package version is tag-derived (setuptools-scm) andserver.jsonis already bumped.Implementation Process
Implemented with AI assistance under the roadmap contract.
Final scope, review, and acceptance decisions were made by the maintainer.