Commit graph

2812 commits

Author SHA1 Message Date
Vitali sharp8n de2bdd62b9 fix(keyboard): route edit keys to scene template search field
Some checks failed
Rust multi-platform build / ${{ matrix.label }} (false, windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Rust multi-platform build / ${{ matrix.label }} (true, false, macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Rust multi-platform build / ${{ matrix.label }} (true, false, windows-aarch64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Has been cancelled
Rust check (native) / ${{ matrix.os }} / 1.94 (macos-latest) (push) Has been cancelled
Rust check (native) / ${{ matrix.os }} / 1.94 (ubuntu-latest) (push) Has been cancelled
Rust check (native) / ${{ matrix.os }} / 1.94 (windows-latest) (push) Has been cancelled
Rust check (native) / cargo-deny (native) (push) Has been cancelled
Rust multi-platform build / ${{ matrix.label }} (false, linux-aarch64, ubuntu-24.04-arm, aarch64-unknown-linux-gnu) (push) Has been cancelled
Rust multi-platform build / ${{ matrix.label }} (false, linux-x86_64, ubuntu-latest, x86_64-unknown-linux-gnu) (push) Has been cancelled
Rust multi-platform build / ${{ matrix.label }} (false, macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
Rust multi-platform build / ${{ matrix.label }} (cargo check only) (android-aarch64, ubuntu-latest, aarch64-linux-android) (push) Has been cancelled
Rust multi-platform build / ${{ matrix.label }} (cargo check only) (android-x86_64, ubuntu-latest, x86_64-linux-android) (push) Has been cancelled
Rust multi-platform build / ${{ matrix.label }} (cargo check only) (ios-aarch64, macos-latest, aarch64-apple-ios) (push) Has been cancelled
Rust multi-platform build / ${{ matrix.label }} (cargo check only) (ios-aarch64-sim, macos-latest, aarch64-apple-ios-sim) (push) Has been cancelled
Version consistency / Verify synchronized versions (push) Has been cancelled
WASM bundle build (#56 — real canvaskit deployable) / build + size-gate canvaskit web bundle (push) Has been cancelled
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Has been cancelled
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Has been cancelled
Backspace, forward-delete, and arrow keys were falling through to the
canvas when the Scene Template (Asset) Center was open, so the search
field could not be edited and the selection behind the overlay could
be deleted. Route these keys to the gallery's focused field for native
parity and add regression tests.
2026-08-21 11:15:28 +03:00
Vitali sharp8n 2a872b1bb8 mcp fix, dark theme for w4c 2026-08-20 09:48:51 +03:00
Kayshen-X 9c810776da chore(release): update Nix release manifest for v0.8.4 2026-08-11 17:17:43 +00:00
Kayshen-X c51d7ed41a fix(ai): raise the deck generation budget 13200 -> 13500
Nine new style guides and the projector-board corpus additions grew the deck
skill set: a deck prompt now resolves 13293 tokens with `design-principles`
(438) included, so at 13200 the Step 3 knapsack dropped `design-principles`
while the report still showed headroom — exactly the ordering alarm
`no_skill_loses_the_budget_race_while_the_budget_has_room` guards. Per that
test's own guidance ("the fix is the phase budget, not this assertion"), raise
Generation to 13500, keeping ~200 tokens of margin over the measured 13293.
2026-08-11 23:38:07 +08:00
Kayshen-X 19d292794c test(op-auth-bridge): accept hardened archives in link-compat test
The reviewed protector localizes `rust_eh_personality` in the hardened Mach-O/ELF
prebuilts, so those archives no longer carry the symbol and link into a Rust host
with no clash — the namespacing shim is a correct no-op there (the host build
already links them). The MSVC COFF pass-through still carries the symbol and must
be namespaced. Assert namespacing only for archives that contain the host
personality, and exercise the idempotency path against the COFF archive that
still has it. Fixes the multi-platform "Test (host, Linux)" failure introduced
by the hardened v0.8.4 artifacts.
2026-08-11 22:28:21 +08:00
Kayshen-X a83e2119fa fix(collab-security): restore correct fixture paths and locator ingress
Resolving an earlier stash conflict picked a stale variant of the boundary
mutation-test fixtures: it used op-host-desktop/src/collab_runtime paths (the
runtime actually lives at op-collab-host/src/runtime, which the gate and
-cases.sh reference) and dropped the pairing-code location blocks the gate
requires in the locator ingress fixtures. Restore both so the baseline
'minimal safe boundary' test and the renewal mutation cases pass again; all 61
mutation tests green.
2026-08-11 22:19:48 +08:00
Kayshen-X 0b7a633cb1 fix(nix): bump jian pin so source builds, regenerate flake.lock (#208)
The tag pinned jian at 453bd70 (2026-08-08), but the code calls
LayoutScene::node_path_at_doc_point_for_fill, only added to jian in 2edfc36
(2026-08-09) — so `nix build …#openpencil` failed to compile. The committed
flake.lock was also older than the flake.nix pins for jian and casement, so
`nix flake show` errored without --no-write-lock-file.

Bump the jian input to 2edfc36 and regenerate flake.lock (jian + casement now
match their flake.nix pins, with correct narHashes). Fixes #208.
2026-08-11 21:52:25 +08:00
Kayshen-X a39406c12b feat(deploy): collab-relay production configs, CN docker firewall, HSM locator
Add production deployment configs for the collab relay and locator: region-split
compose (CN/global), direct nginx gateways and location maps, the CN docker-user
firewall install/verify/validate tooling and systemd unit, and an SoftHSM-backed
locator variant. Extend the collab security/deployment boundary checks to cover
the new artifacts.

The CN application host address is a placeholder (10.0.0.10); substitute the real
private address at deploy time.
2026-08-11 21:46:47 +08:00
op-platform-ci fbcc1d0dc6 feat(desktop): stage signed hardened op-auth ABI v2
Private build id: op-auth-firstparty-v1-20260811d

Source revision: 74e9f3d5dda851fcb5795c35dc3a506404a2d602
2026-08-11 13:29:59 +00:00
Kayshen-X e94b9408c1 docs(release): cover the agent-facing wiring in the v0.8.4 notes
The notes described a purely corrective release, which stopped being true
once deck export, the scene-template catalogue, the style-guide listing and
batch frame export landed on the same version.
2026-08-11 08:13:09 +08:00
Kayshen-X b8b1b4ed7e feat(mcp): expose batch frame export and the deck board list
Exporting every frame on a page meant calling export_item once per frame
and inventing file names; the editor's own batch has resolved collisions,
capped long AI-authored names and skipped hidden frames since v0.8.3.
export_frames reuses plan_frame_exports rather than re-deriving any of it,
so an agent writes what the Export panel writes.

Partial failure is reported, not raised: one unrenderable frame must not
throw away the files that did land.

get_deck_boards is the read half. Slideshow control is deliberately absent:
entering preview is a host mode transition rather than document state, and
a file-backed MCP session has no window to present in. The board list is
the part an agent can act on — verifying a deck before exporting one.
2026-08-11 08:11:39 +08:00
Kayshen-X fbff8857f3 feat(mcp): expose the style-guide catalogue to MCP and the CLI
get_style_guide answers "give me a guide matching this" and searches the
shipped corpus only, so an agent choosing an asset could neither see what
exists nor reach the DESIGN.md files the user imported — the material most
worth picking was invisible to MCP entirely.

list_style_guides merges both, imports first as the Asset Center paints
them, and keeps them apart by id so an import cannot take a shipped guide's
place. Passing id returns that one guide with its markdown, which is what
closes the loop for imports.

No data moved: the canonical registries already live in op-ai-skills, and
only the swatch colours and summary line were ever widget-layer concerns.
The tool reads the host's imported files, so it is a LocalFilesystem
surface and denied online for the same reason list_theme_presets is.
2026-08-11 08:03:39 +08:00
Kayshen-X 507510ee0c feat(mcp): expose the scene template catalogue to MCP and the CLI
The catalogue shipped in v0.8.3 behind File > New from template, so an
agent could only ever start from a blank frame. The 16:9 deck templates
live in it too, which makes it the entry point to the presentation
workflow rather than a convenience.

Adopting a template is its own EditorCommand instead of an authored
subtree insert: a template's boards and the palette they resolve against
have to land in one transaction, and OkWithCommand carries exactly one
command. AdoptSceneTemplate reuses adopt_template_boards, which already
encodes the decision a host without a document loader needs — take over an
untouched starter page, append anywhere else.

The two exhaustive classifiers both demanded a verdict, as designed: the
command is batchable, and it stays an unsupported bulk write inside a
collaboration session.
2026-08-11 07:52:52 +08:00
Kayshen-X 24a6305cae feat(mcp): expose deck export to MCP, the CLI, and the deck skill
The PowerPoint, self-contained HTML and slide-per-page PDF exporters
shipped in v0.8.3 but were reachable only from the desktop File menu, so
an agent could build a deck over MCP and then had no way to hand it to
anyone.

Add export_deck across all three surfaces. The destination argument is
outputPath, not filePath: filePath is reserved workspace-wide for the .op
document a call targets and is intercepted before dispatch, so it would
route the call at a document that does not exist.

The tool writes a file rather than returning base64 — a deck is a
multi-megabyte artifact and the caller wants something shareable — which
makes it a LocalFilesystem surface, denied on the online /mcp for the same
reason save_document is. The CLI resolves a relative --output against the
caller's working directory, since the daemon's is not the caller's.

Delivery guidance goes in the slides skill rather than deck-contract,
which is already at its token budget.
2026-08-11 01:29:15 +08:00
Kayshen-X 4fb4cc0071 chore(release): bump to 0.8.4
v0.8.3's published binaries could never start the public relay; this
release carries that fix, the build guard that keeps a malformed injected
hub from shipping again, the control-plane failure classification, and the
update-check fallback.

The prebuilt op-auth libraries are version-pinned through their signed
provenance, so a 0.8.4 build requires 0.8.4 artifacts; the 0.8.3 archives
are refused rather than silently linked.
2026-08-11 01:01:58 +08:00
Kayshen-X 59be2b27f3 docs(readme): refresh the star-history figures 2026-08-11 01:01:36 +08:00
Kayshen-X 040dac75e2 fix(desktop): fall back to the releases feed when the GitHub API is rate limited
The anonymous GitHub API allows 60 requests per hour per source IP, so
users behind a shared egress can find the quota already spent by strangers.
The probe then reported "cannot reach the release server, check your
network connection" on a network that was working.

The releases Atom feed is served by github.com rather than api.github.com
and is not on that quota, so it answers when the API will not. The API
stays the first choice for its richer response; the feed only covers the
case where the API refuses to answer at all. Drafts never appear in it,
which is the rule the API path already applied.
2026-08-11 01:01:29 +08:00
Kayshen-X 34f757b9a6 feat(collab): classify relay control-plane failures
Every locator call ended in map_err(|_| RelayUnavailable), so an expired
collaboration ticket, a rate-limited hub, an unreachable network and a
malformed response all reached the user as one sentence. Two of those are
not temporary and one is the user's own sign-in state.

Split the HTTP status classes at the control-plane client, map 401/403 to
the existing ticket-expired notice and 429 to a new rate-limited failure,
and leave only genuine transport and protocol faults as relay-unavailable.
This also gives collab.error.rateLimited its first real producer.

Report each stage failure on one credential-free line: only the payload-free
failure enum and a &'static str variant tag are formatted, never the error's
own Display, which is free to grow payload fields later.
2026-08-11 01:01:07 +08:00
Kayshen-X 7073b32a7b fix(collab): reject a whitespace-padded injected hub URL at build time
The published v0.8.3 binaries carried a leading space in both injected hub
endpoints, so the strict runtime endpoint policy parsed them as no usable
hub and the public relay could never start in either region.

The existing guard could not catch it: repository secrets are not exposed
to the CI test job, so the assertion took its "nothing injected" branch on
every run, while the release job that does receive them ran no assertion.
Validate in a build script instead, where neither job can skip it.

A malformed injected endpoint is also a broken build rather than an
outage, so it now reports as not configured instead of temporarily
unavailable — the old copy invited waiting for something that could never
resolve on its own.
2026-08-11 01:00:15 +08:00
Kayshen-X 449f31dd8b fix(ci): avoid release branch tag ambiguity 2026-08-10 16:43:00 +08:00
Kayshen-X e30d842f58 style(editor): rewrap editor_ui_state doc comments under the 800-line cap
editor_ui_state.rs reached 804 lines, tripping the boundary check's 800-line file cap. Rewrap four trailing field doc comments (text unchanged) to land at 800.
2026-08-10 09:56:00 +08:00
Kayshen-X 24e1ad3d05 feat(auth): accept signed-unobfuscated ABI v3 op-auth prebuilt
The reviewed obfuscator that also performed standard hardening was never produced, so ABI-v2/v3 archives could not ship. Add an explicit, signature-bound signed-unobfuscated profile and accept ABI v3 (relay token) across the build-time validator, the audit gate, and the packager. The archive stays Ed25519-signed and ABI-pinned; private Rust symbol strings/paths/debug are retained by design and declared as hardening=op-auth-signed-unobfuscated-v1.
2026-08-10 09:55:00 +08:00
Kayshen-X a92e32a772 feat(web): link the online account menu to the hub MCP token page
The signed-in account dropdown gains an "MCP Tokens" row that opens the
hub portal's /mcp-tokens page in a new tab, so an online user can mint a
per-account MCP token for external clients. It shows only in the
hub-served multi-tenant editor (gated on the ?tenant= param); the native
desktop never sets the flag — its local MCP is tokenless — and a
self-hosted serve-web without a tenant leaves it hidden. New key
account.mcpToken across all 15 locales.
2026-08-10 09:55:00 +08:00
Kayshen-X 3a5f21d672 feat(mcp): drop the per-instance token on the local live MCP endpoint
The local desktop and a self-hosted serve-web daemon now admit any caller
that clears the Host/Origin boundary — a bare MCP client (Codex, an agent
runner) that has only the URL no longer needs the X-OpenPencil-Token the
CLI reads from the discovery file. The token added friction without
closing a hole: it was published in ~/.openpencil/.op-mcp-port and the
ping reply, readable by any local process anyway. The DNS-rebinding
boundary (Host/Origin screening) and CollabGatePolicy are unchanged, and
the online multi-tenant daemon keeps its own per-account Bearer auth in a
separate request loop — this only relaxes the local endpoint.
2026-08-10 09:30:21 +08:00
Kayshen-X 0448aaeac1 test(ai): retry the stderr stress turn past a spawn ETXTBSY race
Writing a stub and exec'ing it from sixteen threads at once lets one
thread's still-open write fd ride another thread's fork() and hold the
freshly-written stub open for write, so execve reports "Text file busy"
and the turn surfaces a spawn error instead of the child's stderr. That
is a parallel write-then-exec harness artifact, not the drain behaviour
under test, and it flaked the stress case on the emulated aarch64
runner. turn_error now retries past the microsecond ETXTBSY window.
2026-08-10 08:55:16 +08:00
Kayshen-X d724c2d7b3 fix(collab): gate the unix-only locator production-check imports
The real production check runs a HSM signing round trip that only
compiles on unix (a cfg(not(unix)) stub returns UnsupportedPlatform),
so its imports, the fixed_expired_claims helper and the
EXPIRED_NOT_BEFORE_UNIX constant are all unused on windows and tripped
clippy -D warnings there. Gate them with cfg(unix), matching the
existing UnixHsmRelayLocatorSigner import. Verified clean via
cargo xwin clippy for x86_64-pc-windows-msvc and unchanged on unix.
2026-08-10 08:49:22 +08:00
Kayshen-X 90798c6f87 style(mcp): rustfmt the tool-schema items assertion 2026-08-10 08:08:40 +08:00
ganondev 42bda72623
fix(mcp): declare items on array properties in tool schemas (#207)
MCP clients such as the VS Code extension reject a tool whose inputSchema has an array property without the items keyword ("tool parameters array type must have items"). Add items to the sections/children arrays of design_skeleton and design_content, and add a test enforcing every array property declares items.
2026-08-10 06:34:36 +08:00
Kayshen-X c4f46d16c7 fix(mcp): box the ImageContent variant to satisfy large_enum_variant
The new image content block widened ToolResponse::Ok far past its Err
variant, so clippy -D warnings failed to compile op-mcp. Box the field
(Option<Box<ImageContent>>) so the variant sizes are close again; the
serializer reads it through auto-deref and only the two constructors and
two test builders needed Box::new.
2026-08-10 06:20:20 +08:00
ganondev db93a2ac74
fix(mcp): return get_screenshot as MCP ImageContent block (#205)
The get_screenshot MCP tool serialized its PNG through OkJson, which
wraps the payload in a text content block. Vision-capable MCP clients
(Copilot, Claude Code) therefore received a large base64 string as text
instead of an image, making the screenshot unusable for visual reasoning
(openpencil issue #204).

- Add ToolOutcome::OkImageContent carrying base64 + mime_type plus an
  optional metadata JSON string
- Thread an optional ImageContent through ToolResponse::Ok and emit it
  as an MCP {"type":"image","data":...,"mimeType":...} content
  block in tool_response_to_json before any text block
- get_screenshot now returns OkImageContent (image/png) while retaining
  image_base64 in its text metadata for the in-app chat-agent path
- Update the get_screenshot schema description to state the use case
  (PNG image for visual verification) without the base64 implementation
  detail
2026-08-10 06:16:27 +08:00
Kayshen-X c658706d04 fix(ai): give the post-exit stderr drain room to be scheduled
The reaped-turn drain wait assumed the pipe was at EOF so the task
"returns at once", but the wait is really for the drain TASK to be
scheduled on a saturated runtime. Under the concurrent stress test (and
the orchestrator's parallel turns) that scheduling latency outran the
two-second bound, the tail read back empty, and a child that explained
itself on stderr surfaced as "(no output captured)". Raise the grace to
thirty seconds: it only has to outlast scheduler starvation while still
capping a genuinely wedged reader. Stress test green 5/5 locally.
2026-08-10 06:10:38 +08:00
Kayshen-X 70fe62e30c fix(ci): resolve cfg(test) submodules under a non-root parent module dir 2026-08-10 05:52:28 +08:00
Kayshen-X 9882f742da docs: expand the v0.8.3 release notes into full narrative sections 2026-08-09 23:33:06 +08:00
Kayshen-X e2cc5bab43 docs: v0.8.3 release notes and README updates in all 15 languages
Add RELEASE_NOTES/v0.8.3.md (real-time collaboration, presentation
decks, runtime asset slimming, device-level theme, the Chrome web
capture inbox) and mirror the feature/roadmap updates — plus the Chrome
Web Store link — across README.md and all 14 translations.
2026-08-09 23:20:13 +08:00
Kayshen-X 936851563c docs(vscode): ready the extension listing for the marketplace
Add the 1024px OpenPencil brand icon, bump to 0.8.3, fill in
categories / keywords / homepage / bugs / license / galleryBanner, and
replace the internal-path dev stub README with a real feature listing
grounded in what the extension does (custom .op editor, MCP config, AI
skills, code generation, chat participant).
2026-08-09 23:20:04 +08:00
Kayshen-X e29c784a5a fix(ci): treat default-filename cfg(test) modules as test-only source
The collab security-boundary scanner excludes external test modules
from its production-source checks, but only recognized the explicit
`#[cfg(test)] #[path = "..."] mod x;` form. A plain
`#[cfg(test)] mod production_check_tests;` (default filename) fell
through and its deterministic test signing seeds tripped the
"signing seed leaked into production source" rule. Resolve the default
`name.rs` / `name/mod.rs` sibling too. All 57 boundary mutation tests
still pass.
2026-08-09 23:19:29 +08:00
Kayshen-X d5b0088cfb fix(canvas): drop an image onto an empty placeholder fills it again
A jian bump made unpainted container bodies opt out of the click
hit-test, which also excluded empty placeholder boxes from image-drop
resolution — a drop over one inserted a fresh node instead of filling
it. Resolve the drop target through the new fill hit-test variant that
keeps empty bodies in the path, and bump the jian pin to carry it.
2026-08-09 22:54:21 +08:00
Kayshen-X 5821a439ce feat(web): fetch peer collaboration avatars through the daemon proxy
The shared widget layer enqueues an avatar fetch per unresolved peer,
but only the desktop host drained that queue — on wasm nobody did, so
every collaborator stayed on their initials fallback while the document,
cursors and presence synced fine. The web host now drains the queue each
frame and resolves each peer through the daemon's POST /api/collab/avatar
proxy (a direct CDN fetch is blocked by the wasm CSP), bounded to three
in flight. The self-account avatar keeps its separate web_auth_sync path
and never enters this queue.
2026-08-09 22:44:10 +08:00
Fini eb02fd2fe0 chore(editor): re-render stale preview artifacts
Brings every preview back to what the current binary produces for the
current document: 80 frame and overview PNGs and 18 card JPEGs. Most
follow the tracking re-alignment and the board re-wrap, but a share of
them had drifted purely because the renderer was rebuilt — the audit
does not distinguish, and does not need to, since the invariant is
simply that a saved image equals a fresh render byte for byte.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 21:17:24 +08:00
Fini ad1ed80a8d test(editor): audit preview artifacts by re-rendering them
Preview PNGs and card JPEGs go stale two ways. Editing a document and
forgetting to refresh is the obvious one. The other leaves no trace at
all: the renderer changes, the document does not, and every previously
saved image is silently wrong while git is clean, the tests are green
and the gate passes. That is what happened to three minimal-keynote
frames whose content matched HEAD exactly but whose pixels no longer
did, because the desktop binary had been rebuilt underneath them.

Nothing but re-rendering and comparing bytes can see the second case, so
that is the only criterion here. It rests on rendering being
deterministic — two runs of one document must be byte-identical, which
`--selftest` checks; if that ever stops holding, this tool's verdicts
are void and the renderer is the thing to fix.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 21:17:01 +08:00
Fini 4064f9c6dd style(editor): re-align template tracking to the ratio cap
Fifteen templates carried negative tracking past the cap the previous
rule failed to enforce — 51 text nodes in all, and nothing else in the
documents moves. minimal-keynote's four are hand-written literals in its
generator, so that one is fixed at the source rather than only in its
output; the rest come out of the shared `text()` clamp.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 21:16:46 +08:00
Fini 98f70579bf fix(ai): compare the CJK tracking cap as a ratio, not a rounded number
The negative-tracking cap was stated as `round(fontSize * -0.02)`, and
rounding it was wrong in both directions: at 76px it let -2 through when
the real cap is 1.52, and at 72px it failed a legitimate -1.4 because the
cap rounded down to 1. A rule that both over- and under-fires is not a
threshold worth tuning, so the comparison is now against the ratio itself
with fractional values allowed.

Two narrowing decisions come with it. The cap applies only to runs that
actually contain Han characters — Latin and numeric display (page
numbers, stat values) legitimately sets -0.03 to -0.05em, and 160 nodes
across the library were being flagged for it. And the 64px boundary is
gone, because below 48px the ratio is already self-limiting.

The clamp sits in `text()`, the one entry every generator goes through:
a single tracking literal is routinely shared by several sizes, so
fixing call sites one at a time is guaranteed to miss some. `trackcheck`
makes the rule enforceable outside the two generators that imported the
kit, which is how the drift got in unnoticed.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 21:16:35 +08:00
Fini 3b9fefde26 fix(ai): compact sub-agent skills before spending the budget
Sub-agent prompts budgeted first and compacted second, so the knapsack
paid for skills the compaction was about to delete — and when the bill
came due it evicted the deck material instead. A weak model then designed
a deck with no deck guidance at all, silently, because the corpus on disk
was still correct. The order is now compaction first, and the wrapper
that made the wrong order one call away is gone rather than kept for
symmetry.

`deck-contract` joins the sub-agent allow-set for the same reason its two
neighbours are there, and the deck budget arm asks the single classifier
instead of comparing widths inline: the hand-rolled bound had no aspect
gate, so a tall 1920-wide page claimed the deck budget and spent it on
slide teaching it could not use.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:57:24 +08:00
Fini 53fc40f305 feat(agent): judge a deck board by its geometry as well as the prompt
Deck centring fired only when the prompt said "deck", so the agentic
loop — which has no prompt to read — left every board top-stacked with a
blank lower half. The geometric half of the judgement is now unioned in
at the point of use, covering the paths that have no request to consult.

Centring is an intent-tier move, though: an asymmetric board can be
exactly what the author composed, and the explicit-`justifyContent`
guard does not catch an author who simply placed content and never set a
distribution. So the geometric half is gated on the roots being this
run's own output, which the fresh and append paths can prove and the
whole-document finalize cannot. The prompt half stays ungated — a user
who asked for a deck stated the intent themselves.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:57:11 +08:00
Fini 4f124b0b1d feat(agent): report deck row overflow instead of clipping it
The horizontal-overflow floor sets `clipContent` on a row whose children
do not fit. On a scrolling screen that is recoverable — the content can
still be scrolled back into view. On a projector board it is content the
audience never learns exists, and the honest fixes (shorten it, re-type
it, split the page) are all decisions this pass cannot make.

So a board now reports the overflow and leaves the row visibly too wide.
The report travels as an echo rather than a repair record, because
nothing was repaired and a record would claim credit for a fix that
never happened: the finalize path notes it onto the user-visible
summary, and a sub-task, which has no summary to write to, logs it.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:56:54 +08:00
Fini f10b660099 feat(agent): hand geometry validation the surface it is validating
Every threshold the geometry collectors use is tuned for a screen, and
the deck collectors that need their own floors land next. Giving them
the form through the single classifier now means each arrives without
measuring the root's width for itself.

No collector branches on the form yet. The debug line is there because
until one does, "the deck floors did not fire" and "the board was never
classified as a board" are the same symptom from the outside.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:56:38 +08:00
Fini 4ae03919f9 refactor(agent): move the surface classifier below the orchestrator
The design lint detectors need the same answer the repair passes do —
what kind of surface is this root — and the detectors sit in a crate
below the orchestrator, so the judge has to live there too. Otherwise
each detector would re-derive the form from a width comparison of its
own, which is precisely the drift the single classifier exists to stop.

Nothing changes behaviourally: the orchestrator re-exports the types so
every existing import path still resolves, `detect_all` classifies its
own root, no detector branches on the form yet, and the plan layer's
mobile-width constant now aliases the classifier's band instead of
carrying a second copy of the number.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:56:12 +08:00
Fini eeb6819c2c feat(ai): teach the corpus how a projector board differs from a page
Deck output kept inheriting page reflexes — screen-sized type, page
margins, a row clipped instead of split — because nothing in the corpus
stated the laws a 16:9 board is read under. `deck-contract.md` states
them once (back-row type floors, density budgets, narrative arc, the
slop bans), and six style guides give the new deck templates the same
authored voice when a request names one.

The generation budget moves to 13200 so the deck material is additive
rather than evicting the knowledge skills it depends on, and a standing
test pins the report to the knapsack's own accounting — a
budget-exhausted drop now provably means the skill did not fit.

The CJK kerning rule lands on minimal-keynote, whose display tracking
was tight enough to collide glyph side bearings at large sizes.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:55:26 +08:00
Fini c37a109e3c refactor(editor): wrap multi-board templates onto rows
Every multi-board template laid its boards out in one long horizontal
strip, so opening a seven-board carousel dropped the user onto a canvas
they had to pan sideways to survey. Three per row matches the deck kit,
and the extra row gap is deliberate: the canvas draws frame names at a
fixed screen-space offset, so at fit-to-screen zoom a plain column gap
lets the second row's labels sit on top of the boards above them.

knowledge-carousel additionally takes hard line breaks in two body
paragraphs. Greedy wrapping put a comma at the start of a line and left
a lone full stop on the last one, which are both CJK line-break faults;
authored breaks remove the engine's freedom to reintroduce them.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:55:00 +08:00
Fini 267a08f7aa feat(editor): register the six deck templates in the picker
Wires the new decks into the catalog, the preview grid and all fifteen
locales, so they are reachable rather than merely present on disk.

The preview cards ship as JPEGs like the rest, which grows the staged
scene-template payload; both figures in the asset notes are refreshed
so the wasm budget stays auditable against a real number.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:54:23 +08:00