Skip to content

feat(studio): inline split composer, inline library, detail modal, surface elevation#185

Merged
vutuanlinh2k2 merged 2 commits into
mainfrom
feat/studio-inline-split
Jul 13, 2026
Merged

feat(studio): inline split composer, inline library, detail modal, surface elevation#185
vutuanlinh2k2 merged 2 commits into
mainfrom
feat/studio-inline-split

Conversation

@vutuanlinh2k2

Copy link
Copy Markdown
Contributor

Summary

Reworks the shared studio-react surface 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: filter Tabs + {n} generations / $x spent stats + GenerationCard grid + EmptyState, no sheet chrome. In-flight rows come from useStudioGenerations.mergedGenerations and render the existing shimmer. Exported from the barrel.
  • generation-detail-modal.tsx (new) — Dialog-based detail view; LibraryDrawer retained for existing consumers.
  • studio.css — split-shell transition classes keyed off a data-library-open attribute (composer width; gallery flex-grow/opacity/transform; max-height:0 clamp when closed to kill the phantom scroll void; prefers-reduced-motion guard).
  • composer-hero.tsxalign?: 'center' | 'start' heading prop; surface elevation via surfaceClassName.
  • Surface elevation across composers: Media Generation card + accordion inner fields on --md3-surface-container-low; accordion panels (composer-shell.tsx ComposerDisclosure) + main composer fields on bg-background.
  • theme/tokens.css — define --md3-surface-container-low in the MD3 bridge as hsl(var(--card)). This satisfies the theme-contract guard (tests/theme/tokens-contract.test.ts) and means any consumer that loads @tangle-network/agent-app/styles gets a sensible elevated-surface default instead of a transparent element. gtm overrides it with its own :root value; creative-agent now inherits the package default.

Testing

Ran the full release gate locally (matches publish.yml auto-release: typecheck → test → build):

  • pnpm run typecheck — pass
  • pnpm run build (tsup) — pass; dist/studio-react/index.d.ts exports LibraryPanel, GenerationDetailModal, and ComposerHero's align prop
  • pnpm run test — the studio theme-contract test passes with the token definition above. Four unrelated failures remain locally (src/sandbox/index.test.ts deferred-profile retry/auth tests, tests/create-agent-app.test.ts scaffolder typecheck) — those files are byte-identical to main on this branch and are timing/load-sensitive locally; they pass on the CI runner.

Release

On merge to main, publish.yml auto-releases a patch bump → 0.43.20, published to npm. gtm-agent then bumps its dep off the local link: to ^0.43.20 in a follow-up PR.

…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 tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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-react subpath (package.json:246). The --md3-surface-container-low token is defined in src/theme/tokens.css:72 as an hsl(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-motion guards both the split-shell transitions (studio.css:115) and the existing animations (studio.css:120). Stepper clamps 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.

value-audit · 20260713T123929Z

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.
@vutuanlinh2k2

Copy link
Copy Markdown
Contributor Author

Addressed the duplication nit from the value audit in 79e7de1.

Extracted the drift-prone identical part — the visible type-filter memo, empty state, and GenerationCard grid — into a chrome-less GenerationGrid leaf (plus a filterGenerations helper), now composed by both LibraryPanel and LibraryDrawer's list branch. The intentionally-divergent chrome (drawer's sheet + vault link + full-width tabs vs the panel's segmented tabs on the card surface) stays per caller. Pure extraction — no behavior change for either consumer, so creative-agent's drawer renders identically. GenerationGrid/filterGenerations are exported from the barrel.

Release gate re-run locally: typecheck ✅, build ✅ (both exported in dist/studio-react/index.d.ts), theme-contract test ✅.

@vutuanlinh2k2
vutuanlinh2k2 merged commit e6517f2 into main Jul 13, 2026
1 check passed
@vutuanlinh2k2
vutuanlinh2k2 deleted the feat/studio-inline-split branch July 13, 2026 12:58
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.

2 participants