Skip to content

Experiment: static vs dynamic parts, and the Slot that lets the caller choose - #56

Open
erykciepiela wants to merge 1 commit into
mainfrom
experiment/static-dynamic-parts
Open

Experiment: static vs dynamic parts, and the Slot that lets the caller choose#56
erykciepiela wants to merge 1 commit into
mainfrom
experiment/static-dynamic-parts

Conversation

@erykciepiela

Copy link
Copy Markdown
Collaborator

Scan of every MDC member recording which of its parts are static (fixed at construction), dynamic (read from the channel), or want to be both — plus a prototype that lets the caller decide. Full note: doc/experiment-static-dynamic-parts.md.

The scan

Genuinely static: typography, elevations, cardActions, chipSet, list, listItem, dataRow, dataCell, layoutGrid, divider.

Genuinely dynamic: the three progress displays, snackbar/banner messages — snackbar is the only component whose text part is dynamic by construction.

Want both, only static available — 16 members:

Part Members
label / caption / title toggleSwitch, filterChip, button, fab, menuItem, menu, card, topAppBar, dialog, simpleDialog, drawer, tooltip
icon iconButton, button, fab
option list select, radioButton, segmentedButton, tabBar
geometry slider min/max, dataTable columns, imageList columns, layoutCell span
content checkbox's label widget (pinned at {}), imageListItem src/label, text-field helper/error text (no path at all)

Two of these the codebase already pays for: iconToggle is an entire component that exists because iconButton's icon can't follow data; and photo-gallery couldn't build an image list from data, so it used displayed $ dynamic \m -> each …, tearing down and rebuilding the whole list on every album change — two static parts cost a reconciled collection.

The API

The staging is already there — construction (config args) → registration (body, chrome on screen) → feed (with/mvu); body $ w is already the no-data peek and body $ with sample $ w the sample-data peek. What was missing is per-part choice of stage:

data Slot i a = Absent | Pinned a | Fed (i -> a)

card { caption: "Quiz" }        -- Pinned: renders at registration, no channel
card { caption: fed _.album }   -- Fed: renders on first feed
card {}                         -- Absent: no caption node

Pinned a is deliberately not Fed (const a) — only Pinned is visible without data, so the type records what is peekable.

Measured

card and topAppBar converted:

  • All 31 demos compile unchanged, no new warnings — the lifting rides the existing ConvertOption tag mechanism.
  • A widget stays polarity-agnostic until a call site actually asks for a dynamic part; the cost is that card/topAppBar are no longer Ocular (precedent: drawer, attrWith).
  • Browser-verified in a new scripts/smoke/tests/photo-gallery.mjs: the fed title updates its own text node in place — a slot is a channel, not a rebuild.
  • spago test and the full smoke suite pass.
  • photo-gallery's app bar now names the open album, retiring its separate headline2 … # tapped stage.

Findings

  1. The dynamic case must be named. { caption: _.album } does not typecheck — an instance chain can only commit on a concrete from, and _.album is still { album :: t | r } -> t at conversion time. Hence fed. Arguably a feature: the config record then says out loud which parts wait for data.
  2. Absent can't fold into the slot — whether a part's node exists is construction-time knowledge, unavailable to a function of i; the three-way split preserves today's behaviour exactly.
  3. Today's static/dynamic boundary coincides with the mvu seam. Every demo writes card {…} $ (pipeline # mvu seed), so chrome is structurally never fed. A dynamic caption forces (card {…} $ pipeline) # mvu seed — the one call-site restructure, visible in the photo-gallery diff.

Alternatives considered and why they lost (twin words, widget slots, row promotion), plus follow-ups in value order, are in the note.

🤖 Generated with Claude Code

…r choose

Scan of all ~45 MDC members recording which parts are static-only, which are
dynamic-only, and which want to be both (doc/experiment-static-dynamic-parts.md).
Sixteen members have a part a real app would drive from data; the codebase
already pays for two of them — iconToggle exists because iconButton's icon
can't follow data, and photo-gallery rebuilds its whole image list per album
because imageListItem's src/label are static.

Prototype: `Slot i a = Absent | Pinned a | Fed (i -> a)` — the three times a
part's value can be known. `card` and `topAppBar` converted; every demo
compiles unchanged, no new warnings. Pinned stays distinct from Fed (const a)
on purpose: only Pinned renders at registration, so the static half of a widget
stays viewable before any model exists.

Findings recorded in the note: the dynamic case must be named (`fed _.album`)
because an instance chain can't commit on an unsolved projection type; Absent
can't fold into the slot because node presence is construction-time knowledge;
and today's static/dynamic boundary coincides with the `mvu` seam, so a dynamic
caption pulls chrome inside it.

photo-gallery's app bar now names the open album, which retires its separate
headline stage; smoke-tested for in-place text-node updates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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