Composition
A component growing flags.
SKILL.md.agents/skills/thorbis-composition/
Follow the skill selection policy and shared Linear baseline. For mobile-app work, the Native contract (D-025) is required.
Scope and required context
Linear baseline: This skill specializes one part of Thorbis; it does not introduce another product grammar. Preserve quiet chrome, one dominant work region, structured density, stable action neighborhoods, contextual surfaces, visible keyboard paths, durable state, and role-adaptive mobile. Use the Thorbis × Linear pattern matrix only when the owning pattern is ambiguous.
Mobile platform gate (D-025): Every mobile-app task requires the Native contract, HIG-001–HIG-022, and relevant live Apple Human Interface Guidelines (opens in a new tab). Apple HIG governs iOS/iPadOS behavior over conflicting web/Linear rules. Cite rule IDs and topic URLs in specifications, component contracts, PRs, and release evidence. Android retains host conventions. Normative requirements and exceptions have one owner: Native.
Flags double the state space. Compose the pieces the surface needs.
This is its own skill. Drop it at .agents/skills/composition.md. Pick this file. Do not average it with the others.
This chapter is code composition. How a *picture* is built lives in UI. How a *video* is framed lives in Cinematic. How a *new surface* joins the shell lives in Product. Do not mix them.
Live: https://design.thorbis.com/skills/composition
Rules is what we ship. Research is the homework. `DESIGN.md` is the constitution. `AGENTS.md` is the contract.
This skill holds: D-002 (the compound does not own chrome), D-004 (use the kit; do not hand-build a dialog), D-005 (a new variant needs a semantic reason and a canonical owner), D-006 (a new primitive is design-system work), D-007 (search the registry), AG-001, AG-003 (use generators and primitives that already exist; fill behavior; do not invent structure).
Homework: John Ousterhout, deep modules; a file that grows past the job gets split. React composition. shadcn is a start; we own the CSS. Base UI when the kit is missing. Grammar, not a second component kit.
thorbis-composition-SKILL.md
First
If a boolean exists to hide a slot the caller could have omitted, delete the boolean.
If two call sites share 90% of the markup, that is success. The 10% is why they are not one component with a variant.
Search the registry before you add a piece. A duplicate is a defect. D-007. AG-001.
Interactive controls use the kit. Do not hand-build a dialog. D-004.
A new variant needs a semantic reason and a canonical owner. destructive is a reason. isInvoice is not. D-005.
A new primitive is design-system work, not a feature shortcut. D-006.
Test
Don’t
<Composer isThread isEditing showNotify tone="job" />Do
<DispatchThreadComposer>
<Composer.Input />
<Composer.Notify />
<Composer.Footer>
<Composer.Submit>Dispatch</Composer.Submit>
</Composer.Footer>
</DispatchThreadComposer>Name the thing after the job: JobNoteComposer, InvoiceLineTable, ArriveSheet, WorkflowCanvas. Not SmartPanel. Not SmartBoard. Not InfiniteCanvas.
Rules
- Export pieces, not configurations. The caller omits what it does not need.
- A variant is a name (
JobNoteComposer), not a flag (variant="job"). - Context holds state, actions, meta. Do not leak setters. Expose
save, notsetDirty. - Lift state so a toolbar Save and a dirty-dot can see it. Do not sync two
useStates. - React 19: refs are props. No
forwardRef. - Feature logic stays out of a shared primitive. A primitive does not know what a job is. Do not put feature copy in it.
- Search the registry before you add a piece. D-007. AG-001.
- A compound does not paint
AppShell. Chrome is the page’s. The compound is the work on the sheet. D-002. - Use generators and primitives that already exist. Fill behavior. Do not invent structure. AG-003.
- One button. One field. One dialog. Variants are semantic (
destructive,ghost), notisInvoice. - Do not import a second component kit. Do not hand-roll a menu or a tabs list.
Compound pattern
Provider state, actions, meta
Frame
Input
Footer
Submit
CancelCallers omit what they do not need. No showX props.
Context exposes verbs (save, reset, busy) and facts (dirty, error). It does not expose setState.
If a flag starts to branch markup, split. Do not wait until you “feel tidy.”
Keep one component when every caller needs the same slots in the same order.
A new occupant of the sheet (WorkflowCanvas, InvoiceLineTable) is a named compound. It is not a page-local shell. D-012. Inventing the occupant: Product.
Worked: a note on a job
The ask: a composer that can be a thread, a job note, or an invoice comment, with optional notify, optional attachments, optional “also dispatch.”
That is three compounds, not one with flags.
JobNoteComposer, note on a job. Footer is Save. Notify is omitted.DispatchThreadComposer, thread. Notify is a piece. Submit is Dispatch.InvoiceCommentComposer, comment on an evidence page. No notify. No dispatch.
Shared pieces live in Composer.Input, Composer.Footer, Composer.Submit. Shared does not mean one mega-component.
The primitive under them is the kit’s field and commit. The composer does not know what a job is beyond the name on the submit.
Worked: a table growing flags
DataTable with isInvoice, showTotals, stickyHeader, compact, hideFilter is soup.
- Density is a view on the view bar, not a flag. D-015. Preserve the shared Linear baseline.
- Totals are
InvoiceLineTable, a named compound, because the math is the job. - Sticky header is the pattern’s, not a boolean the page passes in.
- Filter lives in the view bar, not inside the table.
If DataTable already exists, fill it. If invoice math does not fit, name InvoiceLineTable and say why. Do not add isInvoice.
Split
Split when a flag starts to branch markup.
Keep together when the slots are the same and the difference is data.
A primitive that grows a tone="job" is a primitive that now knows the domain. Move the domain up. Leave the primitive stupid.
Feature code consumes primitives. It does not fork a color, a radius, or a dialog “for this invoice.” D-003. D-004.
Never
variant: "job" | "thread"that branches in every child- Feature logic in a shared primitive
- A second kit imported beside the one we own
showX/hideY/isZgrowing past two- A compound that paints its own page chrome
- A boolean to hide a slot the caller could have omitted
- Hand-building a dialog that the kit already has. D-004
- A new primitive on a feature ticket. D-006
forwardRef- Two
useStates kept in sync “for now”
Audit
- Registry searched. D-007. AG-001
- Kit dialog, menu, tabs reused. D-004. AG-003
- No
showXfor a slot the caller can omit - Named after the job, not
Smart* - Context exposes verbs and facts, not setters
- Does not paint
AppShell. D-002 - New variant has a semantic reason and an owner. D-005
- Primitive still does not know what a job is
Two failures: do not ship.
Done
Name the compound, the pieces you exported, and the boolean you deleted. Name the primitive you reused. If you named a gap instead of inventing a shell, that is success.
Applied bookmark research
Reviewed 2026-09-06. These are Thorbis applications and review exercises, not replacements for DESIGN.md, COMPONENTS.md, or the owning skill. Linear remains the operational baseline. External advice that conflicts with the constitution stays out.
These worked examples are not a claim that the product behaviors have been implemented or verified.
Make invalid combinations difficult to express
Owning skill: composition.
Independent loading, success, and error flags can contradict each other; presentation and business logic become tangled as a component grows.
Choose an explicit state model and a semantic composition API. Reuse the approved primitives, keep domain operations outside them, and locate loading boundaries according to the work they protect. Documentation names which variants actually ship.
Thorbis example: A payment dialog composes an approved trigger, content, fields, and actions around a domain-owned operation. The button receives one coherent request state, not several competing booleans and a private set of colors.
Verify: Exercise each valid state and transition, keyboard and focus behavior, controlled and uncontrolled contracts where supported, server-rendered output, and slow or missing data. Check provenance and license before source adoption.
Do not import: No arbitrary color escape hatch, duplicate component kit, unregistered dependency, or migration from Astro merely because a source uses RSC.
Evidence: React handbook (opens in a new tab); components.build (opens in a new tab); Component Architecture for React Server Components (opens in a new tab); shadcncraft (opens in a new tab). Source access limits.
All skills
The bundle includes every skill. Use Resources to choose the smallest context for a task.