feat(studio): inline split composer, inline library, detail modal, surface elevation#185
Conversation
…rface elevation - LibraryPanel: inline asset gallery replacing the slide-over list view; segmented filter tabs on the card surface - ComposerHero: "Media Generation" heading + align prop (centered focus vs left when library open) - GenerationDetailModal: dialog-based detail view (replaces the drawer for the detail case) - video composer: aspect ratio + reference image URL on one row - surface elevation: composer card + accordion inner fields on md3-surface-container-low; panels/main fields on background - studio.css: split-shell transition classes
tangletools
left a comment
There was a problem hiding this comment.
🟡 Value Audit — sound-with-nits
| Verdict | sound-with-nits |
| Concerns | 2 (2 weak-concern) |
| Heuristic | 0.0s |
| Duplication | 0.0s |
| Interrogation | 235.4s (2 bridge agents) |
| Total | 235.4s |
💰 Value — sound-with-nits
Adds additive presentational leaves (inline gallery + detail modal + split-shell CSS + MD3 surface ladder) so a host app can compose an inline split studio without touching the default shell — sound and in-grain; one bounded duplication of the drawer's list view.
- What it does: Adds two new chrome-less leaves to studio-react: LibraryPanel (filter tabs + stats + GenerationCard grid + EmptyState, no sheet) at src/studio-react/library-panel.tsx:18 and GenerationDetailModal (Dialog wrapping the existing GenerationDetail) at src/studio-react/generation-detail-modal.tsx:11. Adds split-shell transition CSS keyed off a parent's data-library-open attribute (composer shrinks 620px
- Goals it achieves: Let a consuming product (gtm-agent#572) build an inline split studio — composer slides left, inline gallery grows in from the right — by composing the existing leaves, while the monolithic StudioWorkspace stays intact for current consumers (creative-agent). Secondary: tighten composer visual hierarchy (column tabs, elevated prompt, MD3 surface ladder) and make the composer card tone-match a host a
- Assessment: Coherent and squarely in the grain of AGENTS.md's shell-framework rule (capability is presentational shell mechanism, domain stays a typed prop, additive barrel export, default consumer untouched). The new modules correctly REUSE existing primitives rather than reimplementing them: LibraryPanel reuses GenerationCard + EmptyState + TYPE_CONFIG; GenerationDetailModal reuses GenerationDetail; Library
- Better / existing approach: Searched src/studio-react for existing inline-gallery/split-shell primitives (glob'd the dir, read studio-workspace.tsx, library-drawer.tsx, library-panel.tsx, generation-detail.tsx). No existing inline split composer or chrome-less gallery — this is genuinely new presentational surface, not a reinvention. The detail-modal does NOT reimplement detail rendering; it wraps the existing GenerationDeta
- Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 2
- Bridge warning: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content
🎯 Usefulness — sound-with-nits
Coherent presentational split-shell surface that follows the package's documented leaf-export seam; the legacy drawer shell is untouched and an external consumer is imminent.
- Integration: All new modules are exported from the barrel (src/studio-react/index.tsx:24,27) and reachable via the existing
./studio-reactsubpath (package.json:246). The--md3-surface-container-lowtoken is defined in src/theme/tokens.css:72 as anhsl(var(--card))alias, which is the correct shape for the contract guard — aliases re-theme via the cascade and must NOT be redefined in the dark block (test - Fit with existing patterns: Fits the codebase's documented seam. index.tsx:6-8 states the design: 'StudioWorkspace is the ready-to-mount shell; the leaf components are exported so a product can compose its own shell.' This PR adds exactly those leaf pieces (LibraryPanel, GenerationDetailModal, ComposerHero align/surfaceClassName props) for a product (gtm-agent) that wants to compose its own inline split shell instead of moun
- Real-world viability: Holds up past the happy path. The closed-gallery state hard-clamps
max-height:0+overflow:hidden(studio.css:97-98) with an inline comment explaining why (kills phantom scroll void from the grid's min track width) — a real edge case handled.prefers-reduced-motionguards both the split-shell transitions (studio.css:115) and the existing animations (studio.css:120).Stepperclamps with Mat - Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 1
🎯 Usefulness Audit
🟡 Filter + stats + card-grid rendering duplicated between drawer and panel [problem-fit] ``
The list view inside LibraryDrawer (src/studio-react/library-drawer.tsx:77-127 — stats row, TYPE_CONFIG tabs, EmptyState, GenerationCard grid) and the body of the new LibraryPanel (src/studio-react/library-panel.tsx:36-87) render the same logical content with near-identical JSX. Two copies will drift. The drawer could compose LibraryPanel for its list section; the small differences (drawer has a Vault button + full-width unstyled tabs, panel has segmented tabs on the card surface and no Vault li
💰 Value Audit
🟡 LibraryDrawer and LibraryPanel duplicate the filter+stats+grid+empty-state list view [duplication] ``
library-drawer.tsx:38-41 and library-panel.tsx:31-34 hold the identical visible useMemo; library-drawer.tsx:79-127 and library-panel.tsx:36-86 both render a stats row ({n} generations / $x spent), a TYPE_CONFIG Tabs list, the same EmptyState config, and the same grid. The drawer's real purpose (sheet chrome + inline detail-pane navigation + vault link) is NOT duplicated by the panel — only the inner list view is. Cleaner: extract a chrome-less leaf (props: gener
What this audit checks
It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.
| Pass | What it asks |
|---|---|
| Heuristic | Vague title? Whitespace-only or cruft-bearing diff? (content signals only) |
| Duplication | Do added function/class names already exist elsewhere in the repo? |
| Value Audit | What does it do? What goal does it achieve? Is it good? Better architecture or already-exists? |
| Usefulness Audit | Does it integrate and fit? Will it hold up in real use and actually get used? |
Findings are concerns, not blocks — the human reviewer decides what to do with them.
LibraryDrawer's list view and LibraryPanel rendered an identical visible-filter useMemo, empty state, and GenerationCard grid. Extract that chrome-less grid into GenerationGrid (+ a filterGenerations helper) and compose it from both, so the grid and its empty-state copy live in one place. The divergent chrome — the drawer's sheet + vault link + full-width tabs vs the panel's segmented tabs — stays per caller. No behavior change for either consumer.
|
Addressed the duplication nit from the value audit in 79e7de1. Extracted the drift-prone identical part — the Release gate re-run locally: typecheck ✅, build ✅ (both exported in |
Summary
Reworks the shared
studio-reactsurface from a slide-over drawer model into an inline split panel: a composer that can sit centered in focus mode and, when a library panel opens beside it, slides left while an inline gallery grows in from the right. Adds the presentational pieces + transition CSS that a consuming app composes; the orchestration (open/close state, auto-open on generate) lives in the app.Consumed by gtm-agent (tangle-network/gtm-agent#572).
studio-workspace.tsx(creative-agent's monolithic root) is untouched.Changes
library-panel.tsx(new) — inline asset gallery: filterTabs+{n} generations/$x spentstats +GenerationCardgrid +EmptyState, no sheet chrome. In-flight rows come fromuseStudioGenerations.mergedGenerationsand render the existing shimmer. Exported from the barrel.generation-detail-modal.tsx(new) — Dialog-based detail view;LibraryDrawerretained for existing consumers.studio.css— split-shell transition classes keyed off adata-library-openattribute (composerwidth; galleryflex-grow/opacity/transform;max-height:0clamp when closed to kill the phantom scroll void;prefers-reduced-motionguard).composer-hero.tsx—align?: 'center' | 'start'heading prop; surface elevation viasurfaceClassName.--md3-surface-container-low; accordion panels (composer-shell.tsxComposerDisclosure) + main composer fields onbg-background.theme/tokens.css— define--md3-surface-container-lowin the MD3 bridge ashsl(var(--card)). This satisfies the theme-contract guard (tests/theme/tokens-contract.test.ts) and means any consumer that loads@tangle-network/agent-app/stylesgets a sensible elevated-surface default instead of a transparent element. gtm overrides it with its own:rootvalue; creative-agent now inherits the package default.Testing
Ran the full release gate locally (matches
publish.ymlauto-release: typecheck → test → build):pnpm run typecheck— passpnpm run build(tsup) — pass;dist/studio-react/index.d.tsexportsLibraryPanel,GenerationDetailModal, andComposerHero'salignproppnpm run test— the studio theme-contract test passes with the token definition above. Four unrelated failures remain locally (src/sandbox/index.test.tsdeferred-profile retry/auth tests,tests/create-agent-app.test.tsscaffolder typecheck) — those files are byte-identical tomainon this branch and are timing/load-sensitive locally; they pass on the CI runner.Release
On merge to
main,publish.ymlauto-releases a patch bump →0.43.20, published to npm. gtm-agent then bumps its dep off the locallink:to^0.43.20in a follow-up PR.