SOF-8023: Job Designer UX — plan, mockups, and phases 1–3 - #19
SOF-8023: Job Designer UX — plan, mockups, and phases 1–3#19timurbazhirov wants to merge 34 commits into
Conversation
Prioritized proposals (guided readiness rail, context strip, compute cost estimator, preflight checklist, live run monitor) grounded in the current Job.jsx / ComputeTab / WorkflowTab code, with self-contained HTML mockups in mockups/ - no build step needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
…gner plan - AGENTS.md copied verbatim from mat3ra/agents@c75a1b8 - plan/README.md from mat3ra/agents templates/plan/README.md - plan/upcoming/UX-574-Job-Designer-Guided-Designer-Plan.md: three-phase implementation plan for the UI/UX proposals in UIUX_IMPROVEMENTS.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Renamed from UX-574 - SOF-8023 (Job Designer UX update: guided designer flow) is the implementation ticket for this plan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
…ots via LFS - Design-language section: cove theme/palette audit (skeletal paletteDark, status-color contrast defects, ButtonMultiSelect resync), new primitives table (StatusChip, SelectableCard, MetricTile, SegmentedMeter, ...), theme adoption + CSS custom-property bridge - Phase 1.5: design-language groundwork; phases now name the cove primitives - plan/context: current-state + mockup screenshots, tracked with Git LFS Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Repos are public but the tracker is private - file names now carry a YYYY-MM-DD date, the Jira key stays inside the document. Naming convention updated in AGENTS.md section 6 and plan/README.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Self-review additions: parent-job placement in the context strip (SOF-7962 flow), rail behavior across the full lifecycle and read-only jobs, visible error state (1.6), save-state honesty (2.6, UX-498), advanced compute options preserved, performance guardrail around persistJob/renderGeneration, accessibility and localization notes, release sequencing order, success metrics with baseline, S/M/L sizes per item, two new open questions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Submit and Terminate were items in the 'Select Job Actions' dropdown,
shown only when job.id && job.isInInitialStatus. Hidden is the worst state
for a control: the reader cannot act on it and is not told why.
- Submit is a header button, always visible while the job is a draft. When
it is disabled it names what is missing ('Select a material (+2 more)'),
from a pure getSubmitBlockers() the readiness rail and submit preflight
will reuse in phase 2.
- Terminate replaces it while the job runs, and now asks before killing
the run instead of acting on one unconfirmed click.
- ErrorBoundary fallback was <div />, so a render error produced a silently
blank page. It is now a card naming what happened, with a reload and a
copyable digest.
- Pass hideComputeSubTab to the workflow pane so the screen offers one
Compute tab, not one per subworkflow.
Note the shouldComponentUpdate addition: this component's mixins only
consider the job entity, so without it the terminate confirmation could
never render.
SOF-8023 phases 1.4, 1.6 and the consumer half of 1.2. Plan updated with a
correction: the UUIDs and status chips of 1.3 render in @mat3ra/wove, not
workflow-designer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
dist/ is tracked here and dist/jobSubmission.js was missing entirely, which Job.js imports. The husky pre-commit hook regenerates and stages it automatically, but only once hooks are installed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
All @mat3ra/* TypeScript packages track dist/ in git and ship it, so a src change committed without the matching dist leaves consumers on the old code - and a brand-new module is worse than stale: its dist file is absent entirely, so the emitted code imports something that does not exist. The husky pre-commit hook covers this, but only once hooks are installed, and most of these repos have no 'prepare: husky install' script - so a fresh clone commits without it. All four SOF-8023 PRs hit exactly this. Divergence from mat3ra/agents@c75a1b8 - worth upstreaming. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
The hook that regenerates dist has never run: husky was not a dependency and no prepare script installed it, so a fresh clone had .husky/pre-commit sitting inert. That is why this and three sibling packages landed src changes with a stale or entirely missing dist - here, dist/jobSubmission.js was absent while Job.js imported it. Adds husky + 'prepare: husky install' (matching cove, the one repo where this works), and drops 'npx lint-staged' from the hook: lint-staged is neither a dependency nor configured here, so arming the hook with that line would have failed and blocked every commit. Linting is enforced in CI regardless. Verified in ive: staging only a src file produced a commit that included the regenerated dist output. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
plan/README.md asks that a document move when its status changes, and that moving it is part of doing the work. Phase 1 is built and on branches, but it sat inside a single document spanning all three phases, so it could not move. Split per phase - the same shape SOF-8024 adopted, and for the same reason. The phase 1 document records what actually shipped and where it diverged: item 1.3 landed in wove rather than workflow-designer, and shipped as the 'Developer info' toggle SOF-8024 specified rather than the CopyId approach this plan proposed; the cove palette defects were worse than audited; and the build output was never being committed at all, which was an unplanned prerequisite. Also records the parallel SOF-8024 effort in the overview, with the three places the two plans overlap and which document is authoritative for each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
The Materials tab was a full-bleed 3D canvas and nothing else, so the things a reader checks before spending core-hours - what formula this actually is, which lattice, how many atoms, where it came from - were only available by leaving the designer. - Tray above the viewer: a chip per material, click to switch, remove the active one, add more. Multi-material switching previously lived inside the Workflow tab, nowhere near the material being looked at. - The batch consequence is now stated rather than implied: a materials set silently turns one job into N, and the designer never said so. - Metadata panel beside the viewer, read through a defensive getMaterialSummary(): the tab already renders a fallback for hosts that pass a plain config, and model getters throw on partial data, so every field is omitted rather than allowed to take the page down. Verified in the standalone demo: Si2 / FCC / a = 3.867 A / 2 atoms / mp-149 linked to Materials Project. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
The designer saves manually but never said whether it needed to: a job with unsaved edits looked exactly like a saved one, and closing the tab lost them silently. The guided-designer mockups show 'All changes saved' in the header - copy that would be worse than the current silence if it were not actually true. - Header indicator: All changes saved / Unsaved changes / Saving, shown only while the job is editable. - beforeunload guard, armed only when leaving would actually lose work - not for read-only jobs, and not mid-save, since warning there teaches people to dismiss the dialog unread. - Both save paths (injected header organism and package-native fallback) now route through one saveJob(), so the flag cannot be cleared by one and missed by the other. Edits are marked in the handlers rather than in persistJob(): that also runs on mount and on entering the Workflow tab, neither of which is an edit. And hasUnsavedChanges joins shouldComponentUpdate - the mixins only consider the job entity, so state this component owns is invisible to them. Explicitly not autosave: that is UX-498's decision and has backend implications. This only stops the interface being quiet about state it already knows. Verified in the demo: 'All changes saved' on load with the guard off; after editing, 'Unsaved changes' with the guard armed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
The numbered tabs implied a sequence while looking identical whether a step was done or untouched, and the actions that complete them lived in a dropdown. The rail shows each step's state and what is chosen; the strip carries those selections onto every screen, so checking which material a job runs on no longer means leaving the step you are on. getJobReadiness is the single source of truth - the rail, the strip, the Submit button and (next) the preflight all read it, so they cannot disagree about whether a job is ready. It covers the shapes the plan called out: material jobs, dataset jobs, multi-material sets, and parent-derived jobs, where the parent supplies the material and the job must not read as 'no material selected'. After submission the creation steps stop being things to do and Monitor/Files take over, replacing the status-based tab jumping in defaultTab. It is a pure derivation that never touches the entity - a test asserts it does not, because Job.jsx runs job.render() from persistJob() and a compute keystroke must not drag a workflow re-render along. The parent job moves into the strip as a chip: it used to be a dismissable Alert above the tabs, which spent a lot of screen on one fact and put 'unset parent' behind an X that reads as 'hide this message'. Behind useGuidedDesigner (default off), per the plan's rollout. The demo has a toggle so both layouts stay reviewable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Two errors from the rail commit: a nested ternary picking the review step's state, and getReviewSummary called above its declaration. Both helpers now sit above getJobReadiness, and the state choice reads as prose rather than a nested conditional. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Submit fired immediately, and anything the job got wrong — a walltime
over the queue cap, a template that will not render, a batch quietly
costing eight times what was expected — surfaced minutes later as a
failed run. Under the guided layout Submit now opens a preflight that
makes those checks while there is still something to change.
- src/preflight/ — ordered checks (inputs, workflow renders, compute
limits, budget, saved) as pure async functions, plus a runner. A check
that cannot be judged returns `skip`, never `pass`: a green "Budget"
row backed by no pricing data would be a lie the reader cannot check.
A check that throws yields a skip row rather than blocking submission.
- Hosts append their own checks (balance, entitlements) through
setDependencies({ preflightChecks }).
- PreflightDialog: fails block and deep-link to the step that fixes them,
warnings need acknowledging, and acknowledgements survive a re-run only
while their warning does.
- src/computeEstimate.ts — core-hours and cost, used by the preflight's
budget row and the context strip's new estimate chip so the two cannot
disagree. Cost is absent, not zero, when no pricing was injected.
- The readiness selector now knows about cluster limits, and the header
Submit reads its blockers rather than calling getSubmitBlockers itself.
Without that the rail showed Compute complete over a preflight that
refused to submit.
- Legacy-layout hosts keep one-click submit; the preflight follows the
same phase-2 opt-in as the rail.
- The demo gains clusters, pricing, limits and a quota: with an empty
cluster list the compute step was unfillable, so the estimate and the
preflight had nothing to judge and could never be reviewed.
Verified in the standalone demo: all-pass submits; 4×32×12 h passes the
limits and fails the budget, with "Reduce resources" landing on Compute;
a 24 h walltime is blocked at the header with the reason in the tooltip;
a warning blocks until acknowledged, then submits. 86 unit tests pass.
Refs SOF-8023.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
The compute redesign lives in @mat3ra/ive; this is the job-designer half of the wiring — the host data ive cannot fetch for itself. The props are inert on ive releases predating phase 2.3, so this lands ahead of that release rather than after it. `runs` is the material count: a batch job runs once per material, and an estimate for one of them would understate the cost by that factor. Also records that src/computeEstimate.ts is a copy of ive's canonical implementation, to be deleted once ive publishes. Refs SOF-8023. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Phase 2 is built across five repos, so its document moves out of upcoming/ per the folder's own convention, with a Status section saying what actually landed and where it diverged from the plan. The divergences worth reading: the estimator became ive's public arithmetic rather than a panel-local helper, with a temporary copy in job-designer and a TODO to delete it; 2.3 is additive and opt-in rather than a rewrite of the compute form; the preflight grew a fourth outcome (skip) for checks the host published no data to judge; the readiness selector had to learn about cluster limits so the rail, the Submit button and the preflight could not contradict each other; and jode needed no change at all, which takes a package out of the release train. Still open is recorded too: queue-derived limits, the same-as-last-job preset, the cove → ive → job-designer publish order, and the fact that nothing in the webapp publishes pricing or quota yet. Open question 1 is downgraded from blocking to "what does the webapp inject" — the UI now degrades honestly without an answer. Refs SOF-8023. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Replaces the status tint on the header icon with cove's JobLifecycleTimeline: Draft → Queued → Running → Finished, each stage carrying its own icon and its timestamp from the status track. The tint is dropped rather than kept alongside — status is now stated once, in the place that says it best. On a draft the timeline also does the work of telling a first-time reader what is coming. Both header paths get it: the webapp's injected EntityHeader organism and the standalone cove fallback. The WorkflowTab's own `iconCls` is a separate surface and is left alone. Needs a @mat3ra/cove release carrying LifecycleTimeline. Refs SOF-8023. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
The job-designer half of phase 3.3. Both live in @mat3ra/workflow-designer and are inert on releases predating them, so this lands ahead of that release rather than after it. Tied to `useGuidedDesigner`: the guided layout is a dark shell, and the workflow designer pinning itself to a light theme inside it is exactly the mismatch `useHostTheme` exists to fix. Refs SOF-8023. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
The job-designer half. A submitted job left the reader on the form they had just finished with; now the designer moves to the monitor — but on the status transition, not on the click. Switching at the click would land on a Results tab the conditional tab map has not enabled yet, because the job is still `pre-submission` until the server says otherwise. ResultsTab gets the job's units and, where the host injects a `getJobLogTail` reader, its log. Without one it says the deployment has no log feed rather than showing an empty box that reads as a silent job. The demo gains a "Job: draft / running" toggle. Its submit API is a stub, so a running job was unreachable and the monitor, the lifecycle timeline past Draft, and the rail's Monitor step could never be reviewed. The container builds its store from the job it is first given, so the toggle also has to remount it. Verified in the demo: toggling to running moves the lifecycle to Draft[done] → Queued[done] → Running[current], swaps the rail's "Review & submit" for "Monitor · Running" and "Files", removes Submit, and renders the monitor over the workflow's five units. Needs a @mat3ra/jove release carrying RunMonitor. Refs SOF-8023. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
All three phases are now built, so the overview keeps only what outlives them — grounding, the cove audit, cross-cutting rules, release order and open questions — and moves last. The phase-3 divergences worth reading: the unit drawer is not built on cove's ResizableDrawer (bottom-anchored, height-only, with its own consumers); the white canvas turned out to be the designer pinning itself to a light theme rather than the flowchart's own colours, so the fix is `useHostTheme` and not just the palette; and the lifecycle timeline needed two states the plan did not have — a failure in place of the finish that never came, and *skipped* rather than *upcoming* for stages a terminated job never reached. Release order now runs four deep: cove → wove → ive / workflow-designer / jove → job-designer. jove is newly in it. Still open is recorded too, including the webapp log-tail adapter the plan predicted would be half of 3.2, and two pre-existing breakages fixed in passing — workflow-designer's unit suite never ran, and jove's lint was failing on a formatting violation. Refs SOF-8023. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Two cross-cutting items the plan asks for and phases 1–3 had skipped.
**Localization.** The plan says new user-facing strings go through an
injectable resolver with English fallbacks rather than being scattered
through components — and the three phases had scattered about 70 of
them. `src/messages.ts` now holds each one with a key and an English
default; hosts substitute their own through
`setDependencies({ translate })`.
Interpolation is the part that matters. "3 materials — runs 3 times" is
not a fragment plus a number: languages place the count differently and
inflect the noun, so every varying message stays one string with named
parameters. Fallback is per key, not per host — a partial translation is
the normal case, and the keys it lacks must still read as sentences,
never as keys. A resolver that throws is caught: a broken host
translator must not take the designer's copy with it.
The catalogue test that asserts no two keys share their English default
immediately found one — "Configure compute" existed twice. Merged.
**Analytics.** The plan wants a baseline before the flag flips, or "did
the redesign help?" is answerable only by opinion. `src/analytics.ts`
declares the events rather than scattering string literals: designer
opened and job submitted (time to first submit), preflight completed
with *which* checks failed, fix-link followed, warning acknowledged,
step selected (where abandonment happens), and terminate-after-submit.
A throwing recorder is swallowed on purpose. Analytics is the least
important thing on the page and must never be what stops somebody
submitting a job.
Both are no-ops without a host, and the standalone demo renders
identical English with no recorder attached — verified live.
Refs SOF-8023.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
…that is gone The e2e suite tested a placeholder: `#job-designer-app`, an `h1`, a `#status-badges-container` and four status badges. None of them exist in the standalone demo — 4 of its 5 scenarios could only fail. Its baseUrl also pointed at port 3002, which is workflow-designer's dev server, so the suite was aimed at a different application entirely. Replaced with the coverage the plan's testing section asks for: the create-job path through the rail, a disabled Submit naming the first thing missing, context-strip navigation, an all-pass preflight, a configuration over the cluster's limits blocked before the preflight, an over-quota job failing it with the fix deep-linking to Compute, a warning holding Submit until acknowledged, the materials tray, the run monitor, and the legacy layout being untouched. Selectors are ids the components set deliberately, never text or classes, so a copy change — or a translation, now that the strings go through `src/messages.ts` — fails the assertion it should rather than the lookup. Cypress is not installed in this environment, so I could not execute the run. Every one of the 30 selectors was instead resolved against the running demo with a headless browser, including the submit tooltip's aria-label reading "Configure compute" and all five preflight rows: 0 failures. The step logic is exercised, but the Cypress harness itself is unverified here and wants a run before anyone relies on it. Refs SOF-8023. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
The four the phases had left: the render-count guardrail, the message catalogue, the analytics events and the e2e suite — plus the ButtonMultiSelect regression the risk table asked for. Each entry says what is genuinely done and what is not: component chrome outside the phase 2–3 surfaces is still hard-coded English, nothing is collecting the analytics events yet, and the Cypress run is unverified in this environment even though every selector was checked against the live demo. Refs SOF-8023. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Cross-cutting items now done tooThe three phases were the visible work; these are the four things the plan asked for around them that the phases had skipped. All in this branch. Performance guardrailThe plan asked for "a regression test that counts It asserts zero calls across a burst of seven walltime keystrokes, and that a whole preflight report renders exactly once — that one is deliberate, and the test pins it at once rather than once-per-check. LocalizationThe plan says new user-facing strings go through an injectable resolver rather than being scattered through components. Phases 1–3 had scattered about 70 of them. Interpolation is the part that matters: "3 materials — runs 3 times" is not a fragment plus a number — languages place the count differently and inflect the noun — so every varying message stays one string with named parameters. Fallback is per key, since a partial translation is the normal case and the keys it lacks must still read as sentences, never as keys. The catalogue test asserting no two keys share an English default immediately found one: Not covered: component chrome outside the phase 2–3 surfaces — legacy tab labels, the error card, dropdown actions. Those are static strings a scanner can lift later; the derived prose here could not have been. AnalyticsThe plan wants a baseline before the flag flips, or "did the redesign help?" is answerable only by opinion. A throwing recorder is swallowed on purpose — analytics is the least important thing on the page and must never be what stops somebody submitting a job. Still needed: the webapp's recorder and a dashboard. The events exist; nothing collects them yet. e2eThe existing suite tested a placeholder app: Replaced with Cypress is not installed in my environment, so I could not execute the run. All 30 selectors were instead resolved against the running demo with a headless browser — 0 failures, including the submit tooltip's And in coveThe risk table's " Generated by Claude Code |
Auditing 2.1's acceptance criteria against the running app found two
things I had recorded as built that were not.
**"A new user can create and submit a job without opening any
dropdown."** They could not. The plan says the "Select …" dialogs open
from Change affordances on their steps; the rail only navigated, so
picking a material or a workflow still meant the actions dropdown — the
exact thing the rail exists to replace. Each step that owns a dialog now
carries a Change (or Choose, when the step is empty) button, labelled
per step so a screen reader hears "Change Material" rather than four
identical "Change"s. It sits beside the step row rather than inside it:
a button within a button is invalid and unreachable by keyboard.
**"Collapses to a horizontal stepper under 760 px."** It did not — the
rail went full-width with the steps still stacked, pushing the step's
own content off a narrow screen. Now a horizontal scrolling strip below
the md breakpoint. Measured: vertical at 900 px and above, horizontal at
700 and 500, with the rail 263 px tall before and 77 px after.
**And a crash the affordances exposed.** Clicking Change threw
`object is not iterable`. This package's types disagree with its own
code: `JobDesignerDialogState` describes `{ isOpen, open, close }`,
which the container declares and the demo passed, while `Job.jsx`
destructures `[open, close]`, which `useReduxDialog` returns and the
webapp therefore flows. Both shapes are real. Nothing caught it because
the only route to those openers was the dropdown, which no test or demo
run ever clicked.
`normalizeDialogHandle` accepts either rather than picking a winner, so
no host has to migrate before the rail ships, and a dialog the host
never wired does nothing instead of throwing. The demo now passes tuples
as the webapp does, and says which dialog would have opened rather than
silently doing nothing — otherwise broken wiring looks exactly like
working wiring.
Verified: both Change buttons fire their openers, keyboard navigation
and aria-current are unaffected, and Change is correctly absent from
Compute (edited in place) and Review (nothing to choose). 111 tests.
Refs SOF-8023.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Checked each phase-2 criterion against the running app rather than
against my own notes. 2.2, 2.4 and 2.6 hold as claimed — the context
chips stay visible on every step, an incomplete job cannot be submitted,
and the save indicator and leave-guard both flip on an edit. 2.1's two
misses are fixed in the previous commit.
2.5 gains a demo toggle for one-vs-three materials, because the batch
multiplier — the thing the plan says surprises readers most — could not
be reviewed with a single material. Verified: three tray chips over "3
materials — the workflow runs 3 times, once per material", with the rail
step and context chip both reading "3 materials — runs 3 times".
Its removal half stays unverified, and the reason is worth a product
decision rather than a workaround: `addRemoveAllowed={!job.id}` switches
the tray's Add and Remove off the moment a draft is saved, while
`editable` still reports the job as editable. That predates this work,
but the guided designer makes it matter — the tray is now the primary
affordance for materials and is inert on any saved draft. Recorded as
open question 5; not changed.
Refs SOF-8023.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
I audited the acceptance criteria against the running app, and two of them were not metI had recorded phase 2.1 as built. Checking each criterion in the browser rather than against my own notes found two that were false, plus a latent crash. "A new user can create and submit a job without opening any dropdown" — was falseThe plan says the three "Select …" dialogs open from Change affordances on their steps. The rail only navigated. Picking a material or a workflow still meant the actions dropdown — the exact thing the rail exists to replace. So the headline acceptance criterion for the whole item did not hold. Each step that owns a dialog now carries Change (or Choose, when the step is empty), labelled per step so a screen reader hears "Change Material" rather than four identical "Change"s. It sits beside the step row, not inside it: a button within a button is invalid and unreachable by keyboard. Correctly absent from Compute (edited in place) and Review (nothing to choose). "Collapses to a horizontal stepper under 760 px" — was falseThe rail went full-width with the steps still stacked, pushing the step's own content off a narrow screen entirely. Now a horizontal scrolling strip below the
The affordances exposed a crashClicking Change threw Nothing caught it because the only route to those openers was the actions dropdown, which no test or demo run had ever clicked. What held up2.2, 2.4 and 2.6 were verified as claimed: context chips stay visible on all three steps; an incomplete job cannot be submitted; the save indicator reads All changes saved → Unsaved changes on an edit with the 2.5 gained a 1-vs-3 materials toggle in the demo, because the batch multiplier — the thing the plan says surprises readers most — could not be reviewed with a single material. Three tray chips over "3 materials — the workflow runs 3 times, once per material", with the rail step and context chip both reading "3 materials — runs 3 times". One thing I did not change, because it is a product decisionA saved draft cannot change its materials. This predates the guided designer, but the guided designer makes it matter much more: the tray is now the primary affordance for materials, and on any saved draft it is inert. Materials may well be intended to bake into the saved job document — so it is filed as open question 5 rather than changed on a guess. It is also why 2.5's "removing the active material selects a sane neighbour" is the one criterion still unverified: the demo's job carries an The rail's Change Material is gated on Generated by Claude Code |
The GitHub Pages deploy only ever publishes `main`: `deploy-bundle` in cicd.yml declares `needs: [publish]`, and `publish` is gated on `github.ref_name == 'main'`, so both jobs are skipped on a branch push. That leaves no way to look at work in progress without checking it out, which is a poor fit for a change set that is almost entirely visual. Netlify (site: mat3ra-job-designer) builds branches and pull requests, so add the configuration it needs. The one incompatibility is the base path: Pages serves from the /job-designer/ subpath while Netlify serves from the domain root, and a wrong base fails as a blank page rather than a build error, because index.html loads and every asset then 404s. Read the base from VITE_BASE, keeping the Pages subpath as the default so the root-served target opts in explicitly. Verified both ways: the default build emits /job-designer/main.js as before, VITE_BASE=/ emits /main.js, and the latter renders the full guided designer when served from a root, with no failed requests beyond favicon. Ref: SOF-8023 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
✅ Deploy Preview for mat3ra-job-designer ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
The Netlify deploy preview failed in 38 seconds with
npm error code EUSAGE
Missing: husky@7.0.4 from lock file
Netlify installs with `npm ci`, which refuses to run when package.json and
package-lock.json disagree. dfd2501 added husky to devDependencies to arm
the pre-commit hook but did not regenerate the lockfile, and nothing caught
it: this repo's CI and README both install with `npm install
--legacy-peer-deps`, which happily reconciles the drift. So the mistake was
invisible everywhere except on deploy. Note the failure is EUSAGE, not
ERESOLVE — `--legacy-peer-deps` does not rescue it, and neither did the
NPM_FLAGS added in the previous commit.
Regenerated with `npm install --package-lock-only --legacy-peer-deps`. The
diff is the husky entry, a pruned transitive `yaml`, and `"dev"` annotations
npm recomputes; no other package changes version. `npm ci --dry-run` now
resolves cleanly, where before it exited at once.
Documented the trap in AGENTS.md, since the asymmetry between the install
CI uses and the install Netlify uses will re-run this exact failure the next
time anyone adds a dependency.
Ref: SOF-8023
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
… install
Both Netlify deploys — production from main and the deploy preview from
this branch — failed in under a minute, before the lockfile fix and after
it. The shared cause is a lockfile entry, present on main too:
"resolved": "git+ssh://git@github.com/Exabyte-io/simpl-schema.git#355fbea"
Netlify's build container has no GitHub SSH key, so `npm ci` cannot fetch
it. Nothing local catches this: our containers and CI carry a git
`insteadOf` rule rewriting ssh://git@github.com/ to https://github.com/,
so the SSH URL silently becomes an HTTPS one and installs fine.
The SSH form is not what anything asked for. Both packages that pull
simpl-schema in — @mat3ra/workflow-designer as a dependency and @mat3ra/move
as a peer — declare it as git+https://; npm rewrote it to SSH on its own
when writing `resolved`, as it does for GitHub URLs. Rewriting it back
matches the declared spec and drops the credential requirement. The
lockfile now holds no git+ssh entries at all, and `npm ci --dry-run` still
resolves.
simpl-schema is dev-only, but skipping dev dependencies is not an option:
vite, @vitejs/plugin-react and typescript are all devDependencies, so the
build needs them.
Ref: SOF-8023
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Netlify deploy preview: wired up, and red for one reasonThe What was actually wrongThree separate faults, found in order. The first two were real and are fixed here:
Correcting an earlier claimI previously reported that this branch builds cleanly with Evidence that cove is the only remaining blockerIn a clean clone ( Every other package — wove, ive, workflow-designer, jove — resolves fine at its published version. Publishing cove unblocks this deploy preview, ive #6 and jove #9 together. Configuration notes
Two things I did not determine: the cause of the very first production deploy failure from Generated by Claude Code |
main reversed two things this branch had built on, so the resolution is more than textual. **dist/ is no longer committed** (7de5cf3). 18 of the 20 conflicts were modify/delete on files this branch had regenerated; all resolved in main's favour and dist/ is now gitignored here. AGENTS.md 1.7.1 documented the opposite policy in detail, so it is rewritten rather than left to rot: this repo lets CI build and publish, while the rest of the @mat3ra family has not all migrated, so it now tells you to check `git ls-tree` per repo instead of assuming either model. **Dependencies moved to published versions** (3983b74). Took main's pins for all 18 @mat3ra packages. Two things git got wrong on its own, which a plain `checkout --theirs` would also have missed: - `.husky/pre-commit` auto-merged into something incoherent — it kept this branch's comment claiming dist/ is tracked and shipped, took main's contradicting action, and silently dropped main's `npx lint-staged` line. Restored to main's version exactly. - This branch added husky and `"prepare": "husky install"` to arm that hook, which existed only to keep the tracked dist/ in sync. That reason is gone, and arming main's current hook breaks every commit: its first line is `npx lint-staged`, and lint-staged is neither a dependency nor configured anywhere in the repo. This is not a prediction — the first attempt at this merge commit was rejected by that hook, which fetched lint-staged@17.3.0 and then failed with "could not find any valid configuration". Dropped husky and prepare rather than ship a hook that cannot run; AGENTS.md now records why it is dormant and what arming it would require. Dropping husky also retires the lockfile drift that broke Netlify's `npm ci`, and taking main's lockfile wholesale reverts the git+ssh rewrite I made earlier on a diagnosis that turned out to be wrong — npm falls back to an HTTPS tarball for public GitHub repos, so that entry was never a problem. Verified after resolution: tsc clean, eslint 0 errors, 111/111 tests, 0 files tracked under dist/, and main's ApplicationDriver -> StandataDriver rename in src/standalone/preloads.ts intact. Not fixed by this merge: cove 2026.8.19-4 still ships none of the six primitives from cove#97, so `build:standalone` — and the Netlify deploy — stay blocked on that release. Unit CI stays green because tsc does not typecheck Job.jsx, where the cove import lives. Ref: SOF-8023 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Correction, and merged
|
The bundle and the Netlify deploy preview could not build: Job.jsx imports
@mat3ra/cove/dist/mui/components/lifecycle/LifecycleTimeline, and the
published cove — 2026.8.19-4, which main pins — ships none of the six
primitives cove#97 adds. Rollup failed on that import in 8.58s.
Per mat3ra/agents#5, this no longer has to wait for cove#97 to merge and a
registry version to ship. cove's release-wip workflow now publishes that
branch's build as a GitHub pre-release tarball, so point the devDependency at
the immutable per-commit asset:
"@mat3ra/cove": "https://github.com/mat3ra/cove/releases/download/wip-03c8439/cove.tgz"
node_modules/@mat3ra/cove now carries lifecycle, status, metric,
selectable-card, numeric-stepper, log-viewer and copy-id, and
`npm run build:standalone` completes in 1m30s where it previously died in 9s.
This pin is deliberately temporary. Per step 4 of that guideline it reverts
to a semver range the moment cove#97 merges and a normal version publishes —
the tarball URL exists only to test pre-merge changes. The tag is per-commit
and immutable, so this asset cannot change content underneath us.
Also syncs the new guideline into AGENTS.md as 1.8.1, verbatim from
mat3ra/agents#5, since the repo's copy is the one people here actually read.
Verified: `npm ci --dry-run` resolves (Netlify installs with npm ci, so a
lockfile it rejects would fail the deploy before building), tsc clean, eslint
0 errors, prettier clean, 111/111 tests, and the bundle emits /main.js under
VITE_BASE=/ as Netlify serves it.
Ref: SOF-8023
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
Deploy preview is livehttps://deploy-preview-19--mat3ra-job-designer.netlify.app No longer blocked on cove#97 merging. Per mat3ra/agents#5, cove's "@mat3ra/cove": "https://github.com/mat3ra/cove/releases/download/wip-03c8439/cove.tgz"
This pin is temporary by design. Per step 4 of that guideline it reverts to a semver range as soon as cove#97 merges and a normal version publishes — the tarball URL exists only to test pre-merge changes. The tag is per-commit and immutable, so the asset cannot change content underneath us. Getting there took three attempts, and two were my own mistakesRecording them because they're the same failure class and cheap to repeat:
The actual fix was to stop regenerating. cove#97 changes no dependencies — its dependency, devDependency and peerDependency sets are identical to main's, and the only Verified, not assumed
What the preview does and doesn't showIt runs job-designer's own work against published versions of the companion packages. The features living in those packages — ive's compute cards and estimate, workflow-designer's unit inspector, jove's run monitor, wove's flowchart theming — are not in it yet. Each needs the same WIP-release treatment. Generated by Claude Code |
Summary
UI/UX work for the Job Designer (SOF-8023): the brainstorm deliverables and all three phases, across six repos.
Everything is behind opt-in flags, defaulted off for hosts and on in the standalone demos. A host that sets nothing sees no change at all.
Phase 1 — de-noise
#job-submit-button). It used to be a dropdown item shown only whenjob.id && job.isInInitialStatus— hidden is the worst state for a control: the reader can't act on it and isn't told why. It's now always visible while the job is a draft and, when disabled, names what's missing.ErrorBoundaryfallback was<div />, so a render error produced a silently blank page.hideComputeSubTab.Phase 2 — guided designer
2.1 Readiness rail (
src/jobReadiness.ts,JobReadinessRail). Numbered tabs implied a sequence while looking identical whether a step was done or untouched. Each rail step carries its state and a line saying what is chosen — Compute · cluster-007 · 1×16 · 04:00:00, Review & submit · 2 steps remaining. Keyboard-navigable,aria-currenton the active step.2.2 Context strip. Checking which material a job runs on used to mean leaving the step you were on. Chips for material / workflow / compute / estimate, each a way back. The parent job moves here from a dismissable
Alertwhose X was really "unset parent".2.3 Compute redesign (cove + ive + here). Four new cove primitives, then cluster cards, presets and a live estimate in ive. This repo's share is the host data ive can't fetch:
clusterMetadata,computeQuota, andruns— a batch runs once per material, and an estimate for one would understate the cost by that factor.2.4 Preflight at submit (
src/preflight/,PreflightDialog). Five ordered checks; fails block and deep-link to the fixing step, warnings need acknowledging, hosts append their own viasetDependencies({ preflightChecks }). There is a fourth outcome, skip, for checks the host published no data to judge — a green "Budget" row backed by no pricing would be a lie the reader can't check. A check that throws also yields skip: our own bug must not stand between a reader and their job.2.5 Materials tray + metadata panel. The Materials tab was a full-bleed 3D canvas and nothing else. Now a chip tray that also states the batch consequence, and a metadata panel.
2.6 Save-state honesty. All changes saved / Unsaved changes / Saving, with a
beforeunloadguard armed only when leaving would actually lose work. Explicitly not autosave — that's UX-498's call.Phase 3 — the living job
3.1 Lifecycle header. The status tint on the header icon is replaced by cove's
JobLifecycleTimeline: Draft → Queued → Running → Finished, each stage with its own icon and its timestamp from the status track. A failed job shows the failure where the finish would have been, and greys stages it never reached — "upcoming" would suggest a terminated job might still run.3.2 Run monitor (jove + cove + here). A submitted job used to disappear. Now the designer moves to the monitor — on the status transition, not the click: switching at the click would land on a Results tab
conditionalTabsMaphasn't enabled yet, because the job is stillpre-submissionuntil the server says otherwise.ResultsTabgets the units and, where the host injectsgetJobLogTail, the log.3.3 Unit inspector + theme parity (workflow-designer + wove + here). Clicking a flowchart unit opens its settings beside the diagram instead of a three-tab bounce, and the workflow designer stops pinning itself to a light theme inside a dark shell.
Three things worth a reviewer's attention
getSubmitBlockersitself. Without both, the rail showed Compute complete over a preflight that refused to submit.shouldComponentUpdateaddition is load-bearing. This component's mixins only consider the job entity, so state it owns (terminate dialog, dirty flag, preflight) is invisible to them and the render is skipped. That bug class bit three times.clusters={[]}(compute unfillable), the job had no_id(Submit permanently blocked on "Save the job"), and a submitted job was unreachable (monitor, lifecycle past Draft, and the rail's Monitor step invisible). All three are fixed in the demo, each with a comment saying why.Docs and plan
UIUX_IMPROVEMENTS.md— proposals A1–F2, from the running app.mockups/— four self-contained interactive HTML mockups.plan/— phase 1, phase 2 and phase 3 sit inreview/, each with a Status section recording what shipped, where it diverged, and what is still open. The overview keeps what outlives them.AGENTS.md— frommat3ra/agents@c75a1b8, plus two divergences worth upstreaming: date-based plan filenames with the ticket inside the document (public repo, private tracker), and §1.7.1 on committed build output.Known debt, stated rather than hidden
src/computeEstimate.tsduplicates@mat3ra/ive'sutils/computeEstimate, which is canonical. The copy exists only because the ive release carrying it hasn't shipped; it has aTODO(SOF-8023)to delete it. The two are identical today.Companion PRs — merge order
StatusChip/CopyId, and six new primitivesVerification
tscclean, eslint 0 errors, 86 unit tests pass. Behaviour was measured in the running demos, not assumed:64 core·h ≈ $5.12, preflight 5 passes;Draft[done] → Queued[done] → Running[current], rail swaps Review & submit for Monitor · Running and Files, Submit disappears, monitor renders over the workflow's five units;🤖 Generated with Claude Code
https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8