feat(rollout): rollout framework presentational layer + Storybook - #881
feat(rollout): rollout framework presentational layer + Storybook#881jmarrxyz wants to merge 31 commits into
Conversation
…ybook Introduce a process-agnostic `features/rollout/` UI layer for paced, plan-aware processes (firmware / reboot / curtailment), extracted in spirit from `features/energy`. Presentational only — driven by fixtures in Storybook; reconciler wiring is a later phase (see PLANS/PROTO_FLEET_ROLLOUT_FRAMEWORK.md). Components (all reuse shared primitives — CompositionBar, Button, Input, Select, ProgressCircular, PageHeaderPopoverPill): - RolloutControls: strategy-variant config panel (all-at-once / batched / pilot-then-continue), controlled, injected between "Apply to" and "Date and time" in a process config modal. - ActiveRolloutStatus: progress-against-plan card modeled on ActiveCurtailmentStatus — composition bar, stat grid, live elapsed timer, grouped issues, capability-gated lifecycle actions (pause/resume/cancel, pilot continue/retry). - ActiveRolloutBanner + stack: inline progress banner matching the fleet/building banner; stacks per running process on Activity. - RolloutPill: persistent header pill + popover. - RolloutColumnState: per-miner phase chip for the process column. Plus rolloutTypes, rolloutDisplayUtils (phase→CompositionBar mapping, ETA math), and fixtures. 14 stories under Proto Fleet/Rollout. tsc clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Address design feedback by leaning harder on shipped components: - RolloutControls: standardize to the single-column field width; move the per-strategy helper text out of inline notes into an info popover on the strategy field (new RolloutFieldInfo, mirroring CurtailmentStartModal's FieldInfoToggle). Paced fields keep curtailment's two-up sub-row. - ActiveRolloutBanner: rebuild on the shared Callout (our standard inline banner) instead of a bespoke banner; intent + process icon per type. - RolloutColumnState: rebuild on StatusCircle + ProgressCircular + text, matching MinerStatus, instead of a bespoke chip. - ActiveRolloutStatus: mirror ActiveCurtailmentStatus' layout vocabulary (section header, big icon + primary lockup, stat-block grid, single composition-bar progress section, top-right lifecycle buttons); drop the grouped-issues block — the large text, buttons, and progress bar carry state and action. No changes to the curtailment implementation. tsc --noEmit clean. 14 Proto Fleet/Rollout stories still green. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
… in-situ stories Address the next design pass: - Auto-retry model (matches curtailment's reconciler): add a `retrying` target phase — the analog of curtailment's DRIFTED→redispatch. Transient failures self-heal through it; a target only reaches `failed` once retries are exhausted. Manual "Retry failed" is kept only on the terminal completed-with-failures record. Shown in the progress bar + column state. - ActiveRolloutStatus: lead the primary lockup with the process *step* (rolloutStageLabel — "Batch 5 of 12" / "Pilot review" / "Completed"), with the lifecycle state as the eyebrow; miner count demoted to a supporting stat. Removed the redundant orange pilot-gate callout — the stage headline, stats, buttons, and bar already carry it. Added an `embedded` prop to drop the card chrome when hosted in a container. - ActiveRolloutBanner: neutral/black process icons (intent still tints the Callout accent). - ViewRolloutModal: summon the progress card in a shared Modal(large) over the current page — check a rollout without losing context (ActivityDetailModal pattern). - Stories: in-situ set (config modal = Apply to + Rollout + Date and time; fleet table with banner + Firmware column; header pill; Activity "Active now"; view-rollout modal), plus ViewRolloutModal stories. Dropped unused issueGroups from the event model. tsc + eslint clean. 22 Proto Fleet/Rollout stories. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
…ose) The view-rollout modal rendered as a full-width card pinned to the top with no dismiss affordance — the card supplied its own section header while the Modal chrome was suppressed (showHeader=false), so it didn't read as a modal. - ViewRolloutModal now lets the shared Modal own the header: pass title + description (scope), restoring the sticky title bar and the close (X) button; dismiss via X / Escape / click-outside as usual. - ActiveRolloutStatus: when `embedded`, skip its own section header so the title isn't duplicated inside the modal body. - ViewRolloutModal stories: render a stand-in fleet page behind the overlay so the modal reads as an overlay over real content. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Address modal-rendering feedback: - New RolloutConfigModal: the bulk-action config surface as a real shared Modal (large) — Apply-to scope + RolloutControls + Date-and-time — instead of a hand-rolled card. Primary CTA (Start/Schedule rollout) sits in the Modal top bar; the body scrolls under the sticky header when it outgrows the viewport; dismiss via close / Escape / click-outside. - ViewRolloutModal: move the lifecycle CTAs (Pause / Cancel remaining / Continue / Retry) into the Modal top bar; the embedded card now suppresses its own button row via a new `hideActions` prop. - Extract rolloutLifecycleActions() as the single source of truth for which lifecycle controls show, consumed by both the card and the modal so they never drift. - Stories: new Config Modal story; the in-situ config story now uses the real modal over a page. Extract useRolloutConfigModalState into its own file (keeps the component file fast-refresh clean). tsc + eslint clean (0 warnings). Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Switch ViewRolloutModal and RolloutConfigModal from the large (1280px) size to the shared Modal default (standard, 640px). Reflow the ActiveRolloutStatus stat lockups from a 4-up row to a 2-column grid so Strategy / order / elapsed / ETA stay legible at the narrower width (no truncation), and shorten the ETA label to "Est. time remaining". tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
…odal - ViewRolloutModal: set forceTitleCollapsed so the title + lifecycle CTAs sit in the sticky top bar as a persistent action bar (not only collapsing there on scroll). - RolloutPill: add an onViewRollout callback — "View rollout" becomes an in-place button (open the modal) when provided, falling back to the detailsPath link otherwise. - Stories: the pill, single banner, stacked banners, fleet-table banner, and Activity "Active now" now each open the ViewRolloutModal in place (per-event for the stacks), so every entry-point bucket demonstrates the modal. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
…tch curtailment colors Address the active-rollout card feedback: - Scope: move the orphaned "Applies to <scope>" line into the stat grid as a "Scope" StatBlock (mirrors curtailment's "Applies to" stat), and drop it from the modal header/description so it isn't duplicated. - Bar + key: collapse the 5-segment bar (which had two amber buckets) into the curtailment shape — Updated (done) / Remaining (in progress + retrying + queued) / Failed. New rolloutProgressSegments() replaces the per-phase rolloutCompositionSegments(); zero-count buckets drop out. Removed the redundant "X of Y updated (%)" line above the bar (now a stat). - Colors: follow curtailment's curtail-phase precedent exactly (curtailProgressColorMap) via a shared rolloutProgressColorMap — done = core-primary-fill (not success-green), remaining = core-accent-fill, failed = critical — applied to both the CompositionBar and the legend dots. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
When embedded in the ViewRolloutModal the status icon butted up against the sticky header divider. Add pt-2 to the embedded card wrapper so the icon clears the top bar. Non-embedded (standalone) card padding is unchanged, so it still matches ActiveCurtailmentStatus' spacing exactly. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Bump the embedded card's top gap from pt-2 to pt-6 so the space between the modal's sticky header divider and the status icon matches the modal's 24px horizontal inset — balanced margins all around. Non-embedded card unchanged. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
The stat lockups were a hardcoded 2-col grid, so they never reflowed. Mark the card body `@container` and drive the grid off the card's width (not the viewport): grid-cols-1 → @xs:grid-cols-2 → @3xl:grid-cols-4. This is the right axis because the card renders in two very different hosts — the ~592px ViewRolloutModal (stays 2-up) and a wide standalone detail pane (goes 4-up) — where viewport breakpoints would misfire. Matches the repo's existing container-query pattern (CompleteSetup). tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
…nergy, activity) Per design feedback, show the active rollout UI where it actually lives: - Firmware settings page: the active rollout card under the "Firmware" page header, matching the settings page chrome/insets. - Energy UI: the rollout card framed like CurtailmentManagementPanel (section header + Edit/Run buttons), so a process rollout reads like active curtailment does today. - Activity page: the active-rollout banners stacked in the feed above the activity table region, each opening the ViewRolloutModal. Reproduces the real page shells (Header token + px-10 insets) around the existing rollout components; no changes to the components themselves. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Per feedback, show the surrounding UI: add an AppShell wrapper that mounts the real NavigationMenu (w-60 sidebar) with the page content inset beside it, and apply it to the firmware-settings, energy, and activity in-situ stories. Uses the same NavigationMenu + primaryNavItems the app uses (same pattern as the existing NavigationMenu story); no component changes. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
…inerStatus Per feedback, the firmware column state should leverage the states already built rather than a parallel vocabulary: - Use the exact in-flight wording MinerStatus shows for these device statuses: "Updating firmware" (DeviceStatus.UPDATING), "Rebooting", "Curtailing" — via a new columnActiveLabel(processType), instead of the invented "Updating". - Add deviceStatusToRolloutPhase() documenting how a real integration derives the phase from the shipped fleetmanagement.v1.DeviceStatus enum (UPDATING/REBOOT_REQUIRED → inProgress, ERROR → failed), so the column can be driven from the same status the fleet table reads. The auto-retry "retrying" phase has no DeviceStatus analog and comes from the plan rollup. - RolloutColumnState now takes processType; docs note it renders MinerStatus's states, not a separate model. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
…tories
Per feedback ("seed it, and display the other content even if dummy"):
- AppShell now seeds the fleet store with read permissions (fleet/miner/rack/
site/curtailment/activity:read) so the permission-gated nav items render —
the sidebar now shows Home / Fleet / Groups / Energy / Activity / Settings
instead of just Home + Settings.
- Added representative dummy content around the rollout card on each page via a
small DummyTable helper: firmware settings gets a firmware-files table,
Activity gets a "Recent activity" table of completed events below the active
banners, Energy gets a curtailment "History" table below the active card.
Stories only; no component or production changes.
tsc + eslint clean.
Co-authored-by: Jared Marrz <jmarr@squareup.com>
Signed-off-by: Jared Marrz <jmarr@squareup.com>
…ption A) Per the approved option A: a Storybook composition showing the existing "Add firmware payload" modal (FirmwareUpdateModal) with the rollout framework's controls added — the firmware-file picker it has today, then the RolloutControls + Date-and-time sections below, with the Start/Schedule rollout CTA in the top bar. Validates the integrated surface without editing the shipped component or its onConfirm contract (real wiring is a follow-on PR). Story-only. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
…tories The in-situ page stories rendered their tables (Firmware files, Recent activity, curtailment History) as bespoke grid <div>s instead of the product's real table components. Rework them to reuse what ships: - Energy History -> real CurtailmentHistory (its own fixture) - Activity feed -> real ActivityTable (ActivityEntrySchema rows) - Firmware files -> shared List (the component MinerList/ActivityTable use) - Fleet table -> shared List, keeping the RolloutColumnState firmware cell - Drop the DummyTable helper. Also add the modal's real upload path to the FirmwareUpdateModal + rollout story (option A): the 'Upload new file' toggle, product/model/version Inputs, and the shipped FileDropZone — so the composition reflects that firmware upload is present in the update-firmware config, not missing. Story-only. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
The isolated wrapper/composition story buckets duplicate what the In Situ collection already shows in real page context: - View Rollout Modal: shown 5 ways in situ (fleet table, header pill, activity active-now, over-the-fleet-page, activity page). - Rollout Pill: shown by the In Situ header-pill story (popover -> View rollout -> modal). - Config Modal: near-identical duplicate of the In Situ ConfigModal story. - Active Rollout Banner (single + stacked): shown across the In Situ fleet table and activity stories, all three process types. Kept the atomic component state-matrices the compositions don't fully exercise: Active Rollout Status (pilot-gate/completed-with-failures), Rollout Controls (all three strategies), Rollout Column State (all phases). Story-only. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
The two fleet-page In Situ stories couldn't be backed by the real fleet listing without heavy store/protobuf wiring, so they misrepresented the surface: - Fleet table: hand-built a List with a bespoke FleetRow type instead of the shipped MinerList / minerColConfig. - View rollout modal (over the fleet page): rendered the fleet page as a gray placeholder box, not a real view. Removed both. The per-miner Firmware column still has a home in the Rollout Column State bucket; the ViewRolloutModal is still shown in situ via the header pill and the Activity stories. Dropped the now-orphaned RolloutColumnState / RolloutTargetPhase / single ActiveRolloutBanner imports and renumbered the remaining sections. Story-only. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Collapse the rollout Storybook down to two buckets per the design steer, so it reads as a small set of real surfaces plus the isolated parts — not a sprawl of near-duplicate stories. In Situ: - Replace the standalone "Config modal" story with a Bulk actions story: the real ActionBar + BulkActionsWidget (the widget miner bulk actions use today) hosting Update firmware / Reboot / Curtail. Each opens its own config surface — firmware grafts the rollout controls onto the shipped "Add firmware payload" modal (composition, no production edit); reboot + curtail (no bespoke product modal) use the generic RolloutConfigModal. - Migrate the two states worth keeping from the deleted "Active Rollout Status" bucket (paused at pilot gate, completed with failures) onto an Activity → Rollout detail surface in the real nav shell. Components (new RolloutComponents.stories.tsx): the abstracted parts in one bucket — Rollout controls as a single functional example (flip the strategy live) + Rollout column state. Delete the now-redundant standalone story files: ActiveRolloutStatus, RolloutControls, RolloutColumnState, FirmwareUpdateModalWithRollout. Add batchedRebootConfig + batchedCurtailmentConfig fixtures for the generic config modal. Story-only; no shipped component or contract touched. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Split the single "In Situ" story bucket into two so launch surfaces and
live surfaces read separately:
- In Situ/Config: the Bulk actions story (launch a process from the
selection bar).
- In Situ/In Progress: Header pill, Firmware settings, Energy, Activity.
Also, per review:
- Drop the Activity active-now and Activity rollout-detail stories.
- Firmware settings page now renders the real settings subnav (the
shipped SecondaryNavigation), matching SettingsLayout chrome.
- Reboot bulk modal omits the "Apply to" section — scope is already
fixed by the selection. RolloutConfigModal.scopeTargets is now
optional; the Apply-to section renders only when provided.
- Bulk Curtail opens the shipped full-screen CurtailmentStartModal
instead of the generic config modal.
- Rollout strategy option labels drop "Update" ("All at once",
"In batches").
Story-only + additive framework tweaks; no shipped component or its
onConfirm contract is edited. tsc + eslint clean.
Co-authored-by: Jared Marrz <jmarr@squareup.com>
Signed-off-by: Jared Marrz <jmarr@squareup.com>
…ailment
RolloutControls now pairs fields two-up wherever there's a natural
partner (strategy + order, batch size + interval, pilot size + the
offline ceiling), falling back to a lone full-width field — the same
lone-vs-pair reflow curtailment uses.
ActiveRolloutStatus: restore structural parity with the shipped
ActiveCurtailmentStatus, which the previous version had drifted from:
- Standalone card uses the same stat grid (tablet:grid-cols-5,
gap-x-12) with single-value StatBlocks (Scope, Strategy, Order,
Est. time remaining) — no more two-line detail lockups.
- Progress section now matches ProgressSection: a summary line
("N of M miners updated (P%)") on the left plus a right-aligned
elapsed readout above the bar, then the CompositionBar, then the
legend — instead of burying percent/elapsed as stat sub-details.
- In the modal (embedded) the stats render as standard label/value
table rows (the ActivityDetailModal SummaryRow pattern via the
shared Row), per review.
Additive rollout layer only; no shipped component edited. tsc + eslint
clean.
Co-authored-by: Jared Marrz <jmarr@squareup.com>
Signed-off-by: Jared Marrz <jmarr@squareup.com>
… active rollout Mirror shipped curtailment's ActiveCurtailmentStatus in the rollout layer: - Add an optional onManage handler to RolloutLifecycleHandlers; when supplied and the rollout is not terminal, render a leftmost "Manage" action (secondary), matching curtailment's Manage gating/ordering. - Thread onManage through ActiveRolloutStatus and ViewRolloutModal, and wire it at every story call site. - Annotate excluded targets in the progress legend (right-aligned "N excluded"), the analog of curtailment's ProgressSection "N unavailable" annotation, since excluded targets never appear in the composition bar. Additive story-only rollout framework; no shipped component edited. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
…e and reboot Mirror the shipped ActiveCurtailmentStatus Storybook layout for the two other active-rollout processes, so each has its own state showcase plus a live animated lifecycle: - New ActiveFirmwareRollout.stories (Scheduled / In progress / Paused / Pilot review / Completed / Completed with failures + Animated firmware lifecycle). - New ActiveRebootRollout.stories (In progress / Paused / Completed / Completed with failures + Animated reboot lifecycle) — reboot is batched with no pilot gate, so it covers only the states it reaches. - Shared activeRolloutStoryHelpers (ActiveRolloutStatusCard + AnimatedRolloutLifecycle) so the two files can't drift, the analog of curtailment's AnimatedCurtailmentLifecycle. - Fixtures for the missing states (scheduled/paused/completed firmware; paused/completed/completedWithFailures reboot). Story-only + additive; renders the shipped ActiveRolloutStatus with fixtures. No shipped component edited. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Move the per-state and animated lifecycle stories for firmware and reboot into the In Situ bucket, rendering each state inside the real app shell: the NavigationMenu sidebar, a page header carrying the shipped RolloutPill, and the shipped ViewRolloutModal opened on the rollout over live product chrome — instead of bare cards on a blank canvas. Firmware and reboot share a new InSituRollout/AnimatedInSituRollout helper so the two can't drift, and the animation logic is unchanged. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
…established in-situ surface Replace the rejected header-pill + ViewRolloutModal overlay treatment with the in-situ surface already established by the In Situ/In Progress "Firmware settings page" story: ActiveRolloutStatus renders inline in the real page body, not as a modal over dimmed chrome. Firmware lifecycle states render on the Firmware settings page (settings subnav + Firmware header + Upload CTA + firmware files table) via a shared FirmwareSettingsSurface, with the rollout card inline above the files table. The shipped FirmwareSettingsPage story now consumes that same surface, so the two share one source of truth and can't drift. Reboot has no settings page of its own (it is a Fleet bulk action), so its states render inline on the Fleet page via FleetSurface, mirroring the firmware inline treatment. Both surfaces provide their own MemoryRouter, so the lifecycle story metas opt out of the global StoryRouter (withRouter: false). Drop the InSituRollout/AnimatedInSituRollout + RolloutPill/ViewRolloutModal helpers; the animation hook is unchanged. Story-only: no shipped product code is touched. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Add the firmware Release channels surface to the rollout-framework Storybook prototype: - FirmwareSettingsSurface gains an optional Files / Release channels TabStrip (shipped Tab component); omitting the tab prop leaves the existing firmware-lifecycle stories rendering exactly as before. - ReleaseChannelsTable: the Release channels tab body — Create CTA over the shared List (Name / Miners / Releases / Last updated + a per-row Manage action). - ReleaseChannelModal: the Manage release channel full-screen two-pane modal (the shell CurtailmentStartModal uses). Left form (General, Firmware file table, Applies-to scope via TargetSelectButton, Rollout via the framework's RolloutControls); right pane is the live coverage preview (fleet-share ring via ProgressCircular, deploy summary, previous rollouts). - Types + fixtures for release channels, plus stories rendering both the tab and the modal in situ on the Firmware settings page. Presentational only: reuses shipped primitives, no product edits and no RPC wiring. tsc + eslint clean. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
…g, simplify channel preview Three UX passes on the rollout Storybook surfaces, all additive under features/rollout/: - Drop the hand-added "Firmware files" heading above the settings-page files table; the shared List already renders its own "N firmware files" count, so the heading was a redundant duplicate (matches shipped Firmware.tsx, which renders the page Header + List with no heading between them). - Move the Energy UI story's page-header actions (Edit settings / Run curtailment) from base to compact, per the button-sizing rule: base is reserved for modal top-bar, dialog, and empty-state actions; page-header and toolbar actions use compact. - Remove the coverage ring and "% of fleet" readout from the Manage release channel preview pane; the preview now leads with the deploy summary. Drops the now-unused fleetPercent field, fixture value, and ProgressCircular import. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
The firmware rollout Apply-to tables (Manage release channel + the generic RolloutConfigModal) render their scope-select rows via the shared TargetSelectButton, which defaulted to the larger base button size. Add an opt-in `size` prop (default base, so the shipped curtailment and schedule modals are unchanged) and pass compact from both rollout modals, matching the button-sizing rule for in-table actions. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
🔐 Codex Security Review
Review SummaryOverall Risk: LOW Findings[LOW] Completed rollouts use viewing time as their completion time
[LOW] Scheduling state ignores the plan timestamp and defaults to an expired date
[LOW] Device-status mapping treats a firmware attention state as active work
NotesThe change is currently unintegrated Storybook and presentational scaffolding, limiting immediate production exposure. No auth, SQL, command execution, plugin, infrastructure, pool-address, wallet, or protobuf-source changes were present. TypeScript and ESLint could not be run because client dependencies were unavailable; Generated by Codex Security Review | |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c192222d25
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| return "Completed with failures"; | ||
| case "inProgress": | ||
| if (event.strategy === "allAtOnce") { | ||
| return "Updating all at once"; |
There was a problem hiding this comment.
Use the process verb for all-at-once rollouts
When a non-firmware rollout is inProgress and uses the allAtOnce strategy, ActiveRolloutStatus calls this helper and renders Updating all at once. The generic rollout controls expose this strategy for reboot and curtailment as well, so those active rollouts would be mislabeled as firmware updates; derive this branch from event.processType like the other phase labels do.
Useful? React with 👍 / 👎.
The Add firmware payload modal's payload picker was an always-present inline radio list that pre-selected the first file and appended an "Upload new file" button beneath it. That does not scale (the list grows unbounded), assumes a default selection, and lets both input methods compete at once. Rework the payload section to: - use the shipped Select (collapses to one row, scrolls internally when opened) instead of an unbounded inline list; - start with no file selected (placeholder), so the primary action stays hidden until the operator chooses a payload; - switch between "Choose existing" and "Upload new" via the shipped SegmentedControl (the DeliveryPicker pattern), so engaging upload demotes/hides the file-select method rather than stacking both. Reuses FileSelectedStatus for the chosen-upload state. Story-only, additive; no shipped component edits. Co-authored-by: Jared Marrz <jmarr@squareup.com> Signed-off-by: Jared Marrz <jmarr@squareup.com>
Reviewable diff: +2698/-2 across 18 files (excludes generated, test, and story files).Summary
Adds a presentational rollout-framework layer + Storybook for fleet rollouts (firmware updates, reboots, firmware release channels), modeled on the shipped curtailment experience. It gives reviewers a runnable reference for the rollout UX direction — the config surfaces, the in-progress lifecycle, and the release-channel management flow — before any of it is wired to live RPCs.
Everything is additive under
client/src/protoFleet/features/rollout/, plus one backward-compatible prop on the sharedTargetSelectButton. No shipped behavior changes. The components are driven by fixtures and rendered in Storybook, so this is a design-exploration branch, not product code. Unrelated in-flightfleetManagementwork in the working tree is intentionally excluded; this branch is rollout-only.How it works
The layer is a set of presentational React components composed almost entirely from shipped primitives, fed by static fixtures:
RolloutControls(strategy / order / batch size / interval), surfaced either in the genericRolloutConfigModalor grafted onto the shipped firmware "Add payload" flow.useRolloutConfigModalStateholds the transient form state;rolloutDisplayUtilsmaps enum values to human labels.TabStripadds a Release channels tab beside Files. The tab body is a sharedList(ReleaseChannelsTable) with a per-row Manage action that opensReleaseChannelModal— a full-screen two-pane modal (the shellCurtailmentStartModaluses) with General / Firmware / Applies-to / Rollout on the left and a coverage summary on the right.ActiveRolloutStatusis the in-situ status card (stat grid, progress bar + key, elapsed / est-remaining, excluded-count annotation), mirroring curtailment'sActiveCurtailmentStatus.ActiveRolloutBannerandRolloutPillare the shell-level banner and header pill;ViewRolloutModalis the read-only detail view.activeRolloutStoryHelpersprovides the shared decorators/fixtures for the lifecycle states (scheduled → in progress → paused → pilot review → completed / completed-with-failures) plus a live animated lifecycle.State lives only in component/store memory for the demo; nothing crosses a network boundary or persists.
Diagrams
flowchart TD subgraph Shipped["Shipped primitives (reused)"] FSTP["FullScreenTwoPaneModal"] LIST["List"] TABS["TabStrip"] TSB["TargetSelectButton (+ size prop)"] PC["ProgressCircular"] CSM["CurtailmentStartModal shell"] end subgraph Rollout["features/rollout (new, presentational)"] RC["RolloutControls"] RCM["RolloutConfigModal"] ARS["ActiveRolloutStatus"] ARB["ActiveRolloutBanner / RolloutPill"] RCT["ReleaseChannelsTable"] RCHM["ReleaseChannelModal"] VRM["ViewRolloutModal"] FX["fixtures + rolloutDisplayUtils + types"] end FX --> RC FX --> ARS FX --> RCT RC --> RCM RC --> RCHM TABS --> RCT LIST --> RCT FSTP --> RCHM CSM --> RCHM TSB --> RCHM PC --> ARS RCT --> RCHM ARS --> ARB subgraph Stories["Storybook (in-situ)"] ST["real AppShell + nav + seeded perms"] end RCM --> ST RCHM --> ST ARS --> ST ARB --> STstateDiagram-v2 [*] --> Scheduled Scheduled --> InProgress InProgress --> Paused Paused --> InProgress InProgress --> PilotReview PilotReview --> InProgress InProgress --> Completed InProgress --> CompletedWithFailures CompletedWithFailures --> [*] Completed --> [*]Areas of the code involved
components/TargetSelectButton/TargetSelectButton.tsxsizeprop (defaults tobase)features/rollout/. Shared by curtailment + schedule modals; default preserves their rendering.features/rollout/RolloutControls.tsx,RolloutConfigModal.tsx,RolloutColumnState.tsx,RolloutFieldInfo.tsx,useRolloutConfigModalState.tsfeatures/rollout/ActiveRolloutStatus.tsx,ActiveRolloutBanner.tsx,RolloutPill.tsx,ViewRolloutModal.tsxActiveCurtailmentStatus; confirm parity is desirable.features/rollout/ReleaseChannelsTable.tsx,ReleaseChannelModal.tsxTabStrip,List,FullScreenTwoPaneModal,TargetSelectButton,RolloutControls.features/rollout/rolloutTypes.ts,releaseChannelTypes.ts,rolloutDisplayUtils.tsfeatures/rollout/rollout.fixtures.ts,releaseChannel.fixtures.tsfeatures/rollout/*.stories.tsx,activeRolloutStoryHelpers.tsxKey technical decisions & trade-offs
FullScreenTwoPaneModal,List,TabStrip,TargetSelectButton,ProgressCircular,SecondaryNavigation,CurtailmentStartModal) instead of reinventing. Trade-off: the rollout surfaces inherit those components' behavior/appearance, including the shipped defaults noted below.TargetSelectButtongained an opt-insizeprop rather than a global restyle, so curtailment/schedule are provably unchanged (default =base; consumer suites green).TabStriptab reads orange (shippedTabStripdefault, same as FleetLayout/Team tabs) where the mock drew it black; kept the component default rather than override.Testing & validation
Validated at the pushed HEAD
c192222din a clean worktree pinned to that SHA (working tree carries unrelated in-flight work, so checks were isolated to committed content):tsc --noEmit— clean (exit 0).eslint . --max-warnings 0(thejust lintclient step, full client) — clean (exit 0). Covers theclient-boundariesimport/consolerules.TargetSelectButtonconsumers,CurtailmentStartModal.test.tsx+ScheduleModal.test.tsx: 81/81 passing, confirming the additivesizeprop doesn't regress shipped modals.console.*, no--no-verifybypass, all 30 commits carrySigned-off-by+Co-authored-by: Jared Marrz.Not run: unit tests for the rollout components themselves (none exist — presentational/fixture-driven) and E2E (slow, no behavioral API change to exercise).