Repository navigation
feat(*): deck template picker and full-frame deck preview for the web ui - #573
Conversation
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: cap the cold-cache deck cover render fan-out before merge.
I found one blocker inline. Otherwise the picker attachment path, sheet identity and session behavior, RPC models/schema/fixtures, full-frame preview versioning, delivery thumbnail fallback, older-gateway and missing-engine behavior, i18n, and the web architecture constraints are consistent.
I covered the project rules and context docs, the full diff, relevant callers and history, backward compatibility, test changes for weakening, and the architecture boundaries. Verification: uv run pytest tests/test_rpc_console.py tests/test_rpc_files.py tests/test_utils_office.py -x (164 passed); npm test in ui-web (187 files, 2561 tests passed); type-check, generated-client check, source-language gate, large-file gate, and git diff --check passed. ESLint had 0 errors and 5 pre-existing warnings in untouched files.
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: bound the cold-cache cover rendering fan-out tracked in the open thread.
The new a0d2e2dcbeae delta only adds the optional PyMuPDF deptry exceptions and updates the two boot goldens. I reviewed that delta and it is sound: uv run --all-extras deptry raven reports no dependency issues, and the web build reports both boot snapshots matching (254 and 255 nodes). The focused Python suite still passes 164 tests.
The prior inline finding remains reachable and unchanged because raven/rpc/deck_templates.py was not modified, so this revision is still blocked on that one item.
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The semaphore fix settles the only open finding: ten cold cover requests now peak at three conversions while the picker continues answering immediately. I replied to the author and resolved my thread.
For this revision I covered the project rules and context, the fix diff and its caller path, relevant history, backward compatibility, test-strength changes, and the architecture boundary. Verification: the independent concurrency reproduction observed a peak of 3; uv run pytest tests/test_rpc_console.py tests/test_rpc_files.py tests/test_utils_office.py -x passed all 165 tests; the source-language gate, large-file gate, and git diff --check passed.
… shows page one A .pptx opened from the workspace used to render inside a viewer that kept its own toolbar under ours, and the frame stopped short of the pane's bottom edge. Now the frame hides the viewer's controls and fills every pixel under the bar, and the bar carries the deck's own two actions: Download, and Show in Finder / Explorer / file manager, worded for the platform the gateway runs on. A reveal that the host cannot perform comes back as the failure sentence in a toast rather than a silent no-op. The frame URL carries the delivery version so a deck delivered again under the same path is fetched again instead of served from the frame's cache. The transcript tile of a delivered deck shows its first page instead of a generic file glyph: the files route accepts render=thumb and answers with a PNG of page one, converted through the same LibreOffice path as the PDF rendering, with the format now a parameter of to_pdf. The file pane opens at 960 instead of 720 and the workspace column may grow to 1200 instead of 760, so a 16:9 page is readable without dragging. Ported from the pre-refactor deck bar branch onto the react tree, with the class names moved under the workspace namespace the gates require.
The composer gains a Deck template button beside the attach control. It opens a sheet of the templates the deck engine ships, shown as their cover pages; a cover opens the template so its pages can be paged through, and "Use this template" attaches it to the turn exactly as an uploaded file would be, so the host's route to the template engine opens on it. When the engine is not installed the sheet says so instead of showing nothing. Three console methods back it: deck.templates.list names the templates with a cover for each, deck.templates.pages renders one template's pages, and deck.templates.pick copies the template into the agent's uploads and answers in the fs.upload shape, so the frontend treats it as one more attachment. The sheet lives in the composer domain with the cp-tpl class prefix, its styles in the domain's own stylesheet, and its strings under gui.tpl.
The dependency hygiene lint read the rasteriser behind the deck template gallery as a direct import of a transitive package. PyMuPDF is pinned by the ppt-engine plugin, which is also the only reason the gallery exists; the import is inside a function behind a find_spec probe, so the core does not have to carry an AGPL dependency for a feature that is absent without the plugin. It joins the three guarded imports already exempted under both rules, with the reason beside it. The boot snapshot goldens gain the template button the composer now renders, in both the stub and the live-without-gateway trees.
A cold gallery asks for every bundled template in one walk, and every miss started its own cover task at once: ten LibreOffice processes and a couple of GB of rendering from a single click on a gateway that is also serving turns. The cover renders now pass through a semaphore of three. That keeps most of the gain of fanning out, since the time of one conversion barely moves past four in parallel, at a third of the cost, and the picker still answers at once with what is on disk while the rest are drawn behind it.
The diff coverage gate read the gallery at 79 percent: the rasteriser helpers, the engine lookup and the failure branches of a cover and a page set had no test of their own. They have one now, against a renderer that draws nothing but leaves a file, so the tests run where PyMuPDF is not installed; the console gains the case where the copy into uploads fails.
2b6befb to
764120a
Compare
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The force-push rebased the already-reviewed feature onto the updated target; the only substantive new branch commit adds focused gallery tests and does not change production behavior. Those tests accurately exercise the previously uncovered discovery, rasterization, cache, failure, and duplicate-start paths. The settled concurrency thread remains resolved.
I covered the current target diff, the new test commit and surrounding implementation, relevant history, backward compatibility, test-strength changes, the repository rules/context, and the existing architecture boundaries. Verification: uv run pytest tests/test_rpc_deck_templates.py tests/test_rpc_console.py tests/test_rpc_files.py tests/test_utils_office.py -x passed all 176 tests; uv run --all-extras deptry raven, the source-language gate, large-file gate, and git diff --check passed.
… tier (#574) ## Summary The route from Raven-Design to the Raven-PPT template engine used to open on the max tier alone. The same request then came back in three unrelated styles across the tiers, and the top one was built on a template the user never asked for. This PR moves the decision to the one thing that actually calls for the engine: a .pptx the user handed over. - **Route on an attached template.** `SubagentRouteConfig` gains `needsFile` (a suffix), a third independent condition beside `needs` and `minTier`. Raven-Design's route to Raven-PPT now declares `needsFile: ".pptx"` and drops `minTier: "max"`. Every tier designs its deck on Raven-Design; the engine is reached only when a .pptx was handed over, either a bundled template picked in the web UI (companion PR #573) or one the user attached. - **Handed over means attached in this turn.** A new `turn_attachments` ContextVar is frozen beside `turn_tier` on the turn path. A direct chat's media all count; a spawn or DAG node counts an attachment only when its task text names that attachment's path or file name. Task text alone never opens the route: a deck brief spells the deck's destination path, the format in words, or a stray file in the directory exactly like a template, and the first text-only scan sent a no-template run into the engine on its destination path. - **Tiers change effort, not product.** medium asks for low reasoning effort, max for xhigh (the highest word a model may know; models that do not know it fall back to their default), and the baseline inherits the host's setting as before. - **Design lane retries after streamed output.** `llmRetryAfterOutput: true` on the design lane's defaults, matching the ppt lane. A max run died at its thirtieth iteration to an upstream idle timeout after the body had already streamed to the host; the lane now retries instead of failing the turn. - **Skill: use what the user handed over first.** Documents and data are the material, pictures are the pictures, and an attached .pptx is the engine's work, routed before the skill is read. Docs: `CONTEXT.md` admission paragraph, `agents/README.md`, regenerated `schemas/subagent.schema.json`. Also removed: the engine's internal decision log (`plugins-dist/ppt-engine/docs/ppt-raven-design.md`), a single-language working journal for the team building the deck lanes that has no reader in a public repository. The maintainer keeps it outside the tree; the two code comments that cited it by path now state their reason in place. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run --frozen pytest tests/test_subagent_routing_backend.py tests/test_agents_design_launcher.py tests/test_config_schema.py tests/test_subagent_registry.py -q` -> 130 passed (after rebase onto main). - `ruff check` / `ruff format --check` on the changed Python files -> clean. - End to end on a full gateway: a high-tier request with a picked template logged `classified for 'Raven-PPT'; routing there` and the engine delivered a 20-page deck on that template in about 60 minutes; the same request without a template logged `no .pptx the user attached was handed over; running here` and stayed on Raven-Design. A brief that only spelled the deck's destination path stayed on Raven-Design. - Effort labels probed against `z-ai/glm-5.3-flash` over OpenRouter: `low`, `high`, `xhigh` all accepted; `max` spent the whole output window on reasoning, which is why the top tier is xhigh. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk - User-visible: medium and high tier deck requests no longer differ in product, only in effort; a deck on a template now requires attaching one. A bound direct-chat handle to Raven-Design stays on Raven-Design even with a .pptx attached (the route is evaluated at dispatch), a known boundary. - `needsFile` is additive; routes that do not declare it are dispatched exactly as before. - Rollback: revert the squash commit; no schema migration or storage change. - [ ] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A
… ui (#573) ## Summary Two deck features for the web UI, both landing on the refactor tree. **Deck template picker.** The composer gets a "Deck template" button beside the attach control. It opens a sheet of the templates the deck engine ships, shown by their cover pages; opening one pages through its slides, and "Use this template" attaches it to the turn exactly as an uploaded file would be. That attachment is what opens the host's route to the template engine (the route reads the attached .pptx, not the tier, see the companion backend change on main). Without the engine installed the sheet says so. Three console methods back it: `deck.templates.list`, `deck.templates.pages`, `deck.templates.pick`. `pick` copies the template into the agent's uploads and answers in the `fs.upload` shape so the frontend treats it as one more attachment. The sheet lives in the composer domain (`cp-tpl-*` classes, domain stylesheet, `gui.tpl.*` strings). **Delivered deck preview, ported from the pre-refactor deck bar branch.** A .pptx opened in the workspace now fills the whole pane under a single bar: the browser viewer's own toolbar is hidden, the frame takes every pixel to the bottom edge, and the bar carries Download and Show in Finder / Explorer / file manager (worded per the gateway's platform; a reveal the host cannot perform surfaces as a toast with the failure sentence). The frame URL carries the delivery version so a re-delivered deck under the same path is refetched. The transcript tile of a delivered deck shows its first page (`render=thumb` on the files route, PNG through the LibreOffice path; `to_pdf` grew a format parameter). The file pane opens at 960 (was 720) and the workspace column may grow to 1200 (was 760). Class names from the original branch were moved under the workspace namespace (`workspace-fill`, `workspace-deck-acts`) to satisfy the class-namespace gate. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `ui-web`: `npx tsc --noEmit -p tsconfig.json` -> no errors; `npx vitest run src/features/desk src/chrome src/features/workspace src/features/transcript src/features/composer src/state` -> 1229 passed, 0 failed; `npx vitest run scripts/gates` -> 160 passed (class namespace, i18n keys, offline coverage, css one owner, fixture shape); `npm run gen:check` -> 192 methods in sync; `npx eslint .` -> 0 errors (3 pre-existing exhaustive-deps warnings in files this PR does not touch). - `ui-tui`: `npm run lint:i18n` -> generated catalogue up to date. - Python: `uv run --frozen pytest tests/test_rpc_console.py tests/test_rpc_files.py -q` -> 156 passed. - Manual: a full gateway built from this tree (Playwright, full Chromium): picker opens, covers render, paging works, "Use this template" attaches the .pptx; a delivered deck opens full-frame with the one bar; Download serves the file; the tile shows page one. Reveal was exercised on a headless host and produced the toast `reveal failed: ... 'xdg-open'`; the darwin `open -R` branch was verified on macOS on the original deck bar branch. - End to end: a high-tier deck request with a picked template routed to the template engine and delivered on that template; the same request without a template stayed on Raven-Design. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk - New RPC methods are additive; the `render=thumb` query is additive on the files route and falls back to the existing behaviour when absent. - The wider default pane (960 / 1200) is the one user-visible layout change outside deck panes; it only affects the first open and the drag ceiling, saved widths are kept. - Rollback: revert the squash commit; no schema or storage migration is involved. - [ ] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A
… tier (#574) ## Summary The route from Raven-Design to the Raven-PPT template engine used to open on the max tier alone. The same request then came back in three unrelated styles across the tiers, and the top one was built on a template the user never asked for. This PR moves the decision to the one thing that actually calls for the engine: a .pptx the user handed over. - **Route on an attached template.** `SubagentRouteConfig` gains `needsFile` (a suffix), a third independent condition beside `needs` and `minTier`. Raven-Design's route to Raven-PPT now declares `needsFile: ".pptx"` and drops `minTier: "max"`. Every tier designs its deck on Raven-Design; the engine is reached only when a .pptx was handed over, either a bundled template picked in the web UI (companion PR #573) or one the user attached. - **Handed over means attached in this turn.** A new `turn_attachments` ContextVar is frozen beside `turn_tier` on the turn path. A direct chat's media all count; a spawn or DAG node counts an attachment only when its task text names that attachment's path or file name. Task text alone never opens the route: a deck brief spells the deck's destination path, the format in words, or a stray file in the directory exactly like a template, and the first text-only scan sent a no-template run into the engine on its destination path. - **Tiers change effort, not product.** medium asks for low reasoning effort, max for xhigh (the highest word a model may know; models that do not know it fall back to their default), and the baseline inherits the host's setting as before. - **Design lane retries after streamed output.** `llmRetryAfterOutput: true` on the design lane's defaults, matching the ppt lane. A max run died at its thirtieth iteration to an upstream idle timeout after the body had already streamed to the host; the lane now retries instead of failing the turn. - **Skill: use what the user handed over first.** Documents and data are the material, pictures are the pictures, and an attached .pptx is the engine's work, routed before the skill is read. Docs: `CONTEXT.md` admission paragraph, `agents/README.md`, regenerated `schemas/subagent.schema.json`. Also removed: the engine's internal decision log (`plugins-dist/ppt-engine/docs/ppt-raven-design.md`), a single-language working journal for the team building the deck lanes that has no reader in a public repository. The maintainer keeps it outside the tree; the two code comments that cited it by path now state their reason in place. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run --frozen pytest tests/test_subagent_routing_backend.py tests/test_agents_design_launcher.py tests/test_config_schema.py tests/test_subagent_registry.py -q` -> 130 passed (after rebase onto main). - `ruff check` / `ruff format --check` on the changed Python files -> clean. - End to end on a full gateway: a high-tier request with a picked template logged `classified for 'Raven-PPT'; routing there` and the engine delivered a 20-page deck on that template in about 60 minutes; the same request without a template logged `no .pptx the user attached was handed over; running here` and stayed on Raven-Design. A brief that only spelled the deck's destination path stayed on Raven-Design. - Effort labels probed against `z-ai/glm-5.3-flash` over OpenRouter: `low`, `high`, `xhigh` all accepted; `max` spent the whole output window on reasoning, which is why the top tier is xhigh. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk - User-visible: medium and high tier deck requests no longer differ in product, only in effort; a deck on a template now requires attaching one. A bound direct-chat handle to Raven-Design stays on Raven-Design even with a .pptx attached (the route is evaluated at dispatch), a known boundary. - `needsFile` is additive; routes that do not declare it are dispatched exactly as before. - Rollback: revert the squash commit; no schema migration or storage change. - [ ] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A
… ui (#573) ## Summary Two deck features for the web UI, both landing on the refactor tree. **Deck template picker.** The composer gets a "Deck template" button beside the attach control. It opens a sheet of the templates the deck engine ships, shown by their cover pages; opening one pages through its slides, and "Use this template" attaches it to the turn exactly as an uploaded file would be. That attachment is what opens the host's route to the template engine (the route reads the attached .pptx, not the tier, see the companion backend change on main). Without the engine installed the sheet says so. Three console methods back it: `deck.templates.list`, `deck.templates.pages`, `deck.templates.pick`. `pick` copies the template into the agent's uploads and answers in the `fs.upload` shape so the frontend treats it as one more attachment. The sheet lives in the composer domain (`cp-tpl-*` classes, domain stylesheet, `gui.tpl.*` strings). **Delivered deck preview, ported from the pre-refactor deck bar branch.** A .pptx opened in the workspace now fills the whole pane under a single bar: the browser viewer's own toolbar is hidden, the frame takes every pixel to the bottom edge, and the bar carries Download and Show in Finder / Explorer / file manager (worded per the gateway's platform; a reveal the host cannot perform surfaces as a toast with the failure sentence). The frame URL carries the delivery version so a re-delivered deck under the same path is refetched. The transcript tile of a delivered deck shows its first page (`render=thumb` on the files route, PNG through the LibreOffice path; `to_pdf` grew a format parameter). The file pane opens at 960 (was 720) and the workspace column may grow to 1200 (was 760). Class names from the original branch were moved under the workspace namespace (`workspace-fill`, `workspace-deck-acts`) to satisfy the class-namespace gate. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `ui-web`: `npx tsc --noEmit -p tsconfig.json` -> no errors; `npx vitest run src/features/desk src/chrome src/features/workspace src/features/transcript src/features/composer src/state` -> 1229 passed, 0 failed; `npx vitest run scripts/gates` -> 160 passed (class namespace, i18n keys, offline coverage, css one owner, fixture shape); `npm run gen:check` -> 192 methods in sync; `npx eslint .` -> 0 errors (3 pre-existing exhaustive-deps warnings in files this PR does not touch). - `ui-tui`: `npm run lint:i18n` -> generated catalogue up to date. - Python: `uv run --frozen pytest tests/test_rpc_console.py tests/test_rpc_files.py -q` -> 156 passed. - Manual: a full gateway built from this tree (Playwright, full Chromium): picker opens, covers render, paging works, "Use this template" attaches the .pptx; a delivered deck opens full-frame with the one bar; Download serves the file; the tile shows page one. Reveal was exercised on a headless host and produced the toast `reveal failed: ... 'xdg-open'`; the darwin `open -R` branch was verified on macOS on the original deck bar branch. - End to end: a high-tier deck request with a picked template routed to the template engine and delivered on that template; the same request without a template stayed on Raven-Design. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk - New RPC methods are additive; the `render=thumb` query is additive on the files route and falls back to the existing behaviour when absent. - The wider default pane (960 / 1200) is the one user-visible layout change outside deck panes; it only affects the first open and the drag ceiling, saved widths are kept. - Rollback: revert the squash commit; no schema or storage migration is involved. - [ ] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A
Summary
Two deck features for the web UI, both landing on the refactor tree.
Deck template picker. The composer gets a "Deck template" button beside the attach control. It opens a sheet of the templates the deck engine ships, shown by their cover pages; opening one pages through its slides, and "Use this template" attaches it to the turn exactly as an uploaded file would be. That attachment is what opens the host's route to the template engine (the route reads the attached .pptx, not the tier, see the companion backend change on main). Without the engine installed the sheet says so.
Three console methods back it:
deck.templates.list,deck.templates.pages,deck.templates.pick.pickcopies the template into the agent's uploads and answers in thefs.uploadshape so the frontend treats it as one more attachment. The sheet lives in the composer domain (cp-tpl-*classes, domain stylesheet,gui.tpl.*strings).Delivered deck preview, ported from the pre-refactor deck bar branch. A .pptx opened in the workspace now fills the whole pane under a single bar: the browser viewer's own toolbar is hidden, the frame takes every pixel to the bottom edge, and the bar carries Download and Show in Finder / Explorer / file manager (worded per the gateway's platform; a reveal the host cannot perform surfaces as a toast with the failure sentence). The frame URL carries the delivery version so a re-delivered deck under the same path is refetched. The transcript tile of a delivered deck shows its first page (
render=thumbon the files route, PNG through the LibreOffice path;to_pdfgrew a format parameter). The file pane opens at 960 (was 720) and the workspace column may grow to 1200 (was 760).Class names from the original branch were moved under the workspace namespace (
workspace-fill,workspace-deck-acts) to satisfy the class-namespace gate.Type
Verification
ui-web:npx tsc --noEmit -p tsconfig.json-> no errors;npx vitest run src/features/desk src/chrome src/features/workspace src/features/transcript src/features/composer src/state-> 1229 passed, 0 failed;npx vitest run scripts/gates-> 160 passed (class namespace, i18n keys, offline coverage, css one owner, fixture shape);npm run gen:check-> 192 methods in sync;npx eslint .-> 0 errors (3 pre-existing exhaustive-deps warnings in files this PR does not touch).ui-tui:npm run lint:i18n-> generated catalogue up to date.Python:
uv run --frozen pytest tests/test_rpc_console.py tests/test_rpc_files.py -q-> 156 passed.Manual: a full gateway built from this tree (Playwright, full Chromium): picker opens, covers render, paging works, "Use this template" attaches the .pptx; a delivered deck opens full-frame with the one bar; Download serves the file; the tile shows page one. Reveal was exercised on a headless host and produced the toast
reveal failed: ... 'xdg-open'; the darwinopen -Rbranch was verified on macOS on the original deck bar branch.End to end: a high-tier deck request with a picked template routed to the template engine and delivered on that template; the same request without a template stayed on Raven-Design.
Relevant tests pass locally
Relevant lint / type checks pass locally
User-facing docs or screenshots are updated when needed
Risk
New RPC methods are additive; the
render=thumbquery is additive on the files route and falls back to the existing behaviour when absent.The wider default pane (960 / 1200) is the one user-visible layout change outside deck panes; it only affects the first open and the drag ceiling, saved widths are kept.
Rollback: revert the squash commit; no schema or storage migration is involved.
Security impact considered
Backward compatibility considered
Rollback path is clear for risky changes
Related Issues
N/A