Skip to content

Stage 4: Composites - #9

Merged
ntucker merged 7 commits into
mainfrom
stage-4-composites
Jul 26, 2026
Merged

Stage 4: Composites#9
ntucker merged 7 commits into
mainfrom
stage-4-composites

Conversation

@ntucker

@ntucker ntucker commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds Stage 4 product composites (PostCard, Comment/CommentThread, Notification, FeedItem, ProfileCard, SettingsPanel, and supporting building blocks) on a compound-first dual API with serializable @reactive/silk-core/models.
  • Closes primitive gaps needed by composites (Card/Surface interactive elevation, Inline direction, Stack rail, StatusDot) and documents the composition standard in docs/COMPOSITES.md.
  • Ships the SocialFeed exit fixture with design polish for feed, profile, notifications, settings, and compact comment threading; registry sync emits consumer-owned composite source.
  • Fixes the composite asChild contract where roots could not compose as a single element: EmptyState.Root no longer wraps its slot in a Center, and PostCard.Root slots through Slot.Root while re-parenting children with Slot.Slottable instead of always rendering its own <article>. FeedItem also stops treating an absent entry as a loading state.
  • Makes layout flow direction a property of the component rather than a prop. Stack is vertical-only and Inline owns horizontal flow, so align/justify keep their standard flexbox meaning at every call site instead of silently swapping visual axis with a direction prop.

Layout primitive change (breaking, pre-1.0)

Surfaced by the EmptyState.Root bug: collapsing the stray Center wrapper into the slotted element required a justify axis on Stack, which exposed that the layout layer had three spellings of centering and an axis that flipped under a prop.

  • Stack loses direction, wrap, and collapseBelow; its recipe is now exactly gap, align, justify, rail, with flex-direction: column in the base class. collapseBelowRulesStack had to go with it — its stretch rule was guarded by :not([data-direction='column']), which would have matched unconditionally once Stack stopped emitting the attribute and silently overridden a configured align.
  • Center is deleted (component, centerRecipe, exports, SilkDefaults key, story). It was a strict subset of Stack/Inline, and Container already owns max-width measure centering.
  • Grid gains justify, mapped to justify-items to pair with its existing alignalign-items. columns always emits 1fr tracks, so justify-content would have been inert on every configuration Grid can express. This means justify distributes content on the flex primitives but places an item within its track on Grid; the nuance is documented in the "Layout axes" table in ARCHITECTURE.md.
  • Horizontal Stack call sites move to Inline, with align/wrap set explicitly wherever the differing defaults (stretch/nowrap vs center/wrap) would otherwise change rendering.
  • docs/PRINCIPLES.md carries an amendment entry, since the charter's rule-of-use requires one to drop Center from the layer-1 vocabulary.

Nothing has been published (both packages are at 0.0.0 with no changelog), so the pending changesets describe the end state rather than a migration no consumer would have lived through.

Test plan

  • yarn build
  • yarn typecheck
  • yarn test (344 tests: silk-core 30, silk 150, docs 164)
  • yarn test:perf
  • yarn test:packed
  • yarn workspace @reactive/silk-native-spike test
  • Storybook: Fixtures → SocialFeed (Normal, Empty, Loading, Long Thread)
  • Storybook: Components/Composite/* smoke variants
  • Storybook: Layout → Stack / Inline / Grid, including Inline CollapseBelow and the Grid justify matrix
  • Confirm node scripts/sync-registry.mjs output matches committed registry sources

Made with Cursor

Ship product composites on a compound-first dual API, serializable core models, registry sync, and a polished social-feed exit fixture.

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 03156b1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@reactive/silk-core Minor
@reactive/silk Minor
@reactive/silk-docs Patch
@reactive/silk-native-spike Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/silk/src/components/FeedItem.tsx
Comment thread packages/silk/src/components/PostCard.tsx
Comment thread packages/silk/src/components/EmptyState.tsx Outdated
Use h2 for SectionTitle so pages with an h1 do not skip a level under Storybook axe gating.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread scripts/sync-registry.mjs Outdated
Comment thread apps/docs/src/fixtures/SocialFeed.tsx Outdated
ntucker and others added 2 commits July 26, 2026 13:03
…s loading state from an absent entry.

PostCard.Root declared asChild but always rendered its own <article>, so a
consumer element could never become the root. It now slots through Slot.Root
and re-parents children with Slot.Slottable, keeping the Stack decoration
inside the consumer's element.

FeedItem treated `entry === undefined` as loading, so a genuinely empty slot
rendered a skeleton that never resolved.

Co-authored-by: Cursor <cursoragent@cursor.com>
…prop.

Stack had a direction prop, so the same align/justify values silently swapped
visual axis depending on a prop often set far from the alignment depending on
it. Rather than renaming the props away from CSS, the flip trigger is removed:
Stack is vertical-only and Inline owns horizontal flow, so the standard
flexbox reading of align and justify is correct everywhere.

- Stack loses direction, wrap, and collapseBelow; its recipe is now exactly
  gap, align, justify, rail, with flex-direction: column in the base class.
  collapseBelowRulesStack had to go with it: its stretch rule was guarded by
  :not([data-direction='column']), which would have matched unconditionally
  once Stack stopped emitting the attribute and silently overridden align.
- Center is deleted. It was a strict subset of Stack/Inline, and Container
  already owns max-width measure centering.
- Grid gains justify, mapped to justify-items to pair with align. Its columns
  always emit 1fr tracks, so justify-content would have been inert.
- EmptyState.Root composes as a single element again, which is the bug that
  surfaced all of this.

Horizontal Stack call sites move to Inline with align/wrap set explicitly
where the differing defaults would otherwise change rendering.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/silk/src/components/CommentThread.tsx
ntucker and others added 3 commits July 26, 2026 13:06
Share Stack/Inline flex keyword maps, drop Grid's identity placement map, always wrap PostCard children with Slottable, and trim redundant conformance guards.

Co-authored-by: Cursor <cursoragent@cursor.com>
… onto @reactive/silk.

Also gate CommentThread continue on onContinue and keep SocialFeed reduced-motion wrapping on the first entry.

Co-authored-by: Cursor <cursoragent@cursor.com>
…in tests.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 03156b1. Configure here.

</>
) : null}
</StatGroupValue>
</StatGroupStat>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Invalid definition list markup

Medium Severity

The convenience StatGroup renders each stat as a div (StatGroupStat) directly under a dl, with dt/dd nested inside that wrapper. A dl may only contain dt and dd (plus script-support elements), so assistive tech and validators may not treat labels and values as paired description-list terms.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 03156b1. Configure here.

@ntucker
ntucker merged commit 3bf1155 into main Jul 26, 2026
3 checks passed
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.

1 participant