Skip to content

SOF-8023: Job Designer UX — plan, mockups, and phases 1–3 - #19

Open
timurbazhirov wants to merge 34 commits into
mainfrom
claude/jd-ui-ux-improvements-99w9fg
Open

SOF-8023: Job Designer UX — plan, mockups, and phases 1–3#19
timurbazhirov wants to merge 34 commits into
mainfrom
claude/jd-ui-ux-improvements-99w9fg

Conversation

@timurbazhirov

@timurbazhirov timurbazhirov commented Aug 16, 2026

Copy link
Copy Markdown
Member

Summary

UI/UX work for the Job Designer (SOF-8023): the brainstorm deliverables and all three phases, across six repos.

This is the designated branch for job-designer, so docs and code live together here. Happy to split the docs out if you'd rather review them separately.

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

  • Submit is a header button (#job-submit-button). It used to be a dropdown item shown only when job.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.
  • Terminate replaces it while running, and asks first — it was one unconfirmed click that kills a running job.
  • Visible error state. The ErrorBoundary fallback was <div />, so a render error produced a silently blank page.
  • One Compute tab — passes 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-current on 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 Alert whose 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, and runs — 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 via setDependencies({ 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 beforeunload guard 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 conditionalTabsMap hasn't enabled yet, because the job is still pre-submission until the server says otherwise. ResultsTab gets the units and, where the host injects getJobLogTail, 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

  • The readiness selector had to learn about cluster limits, and Submit had to stop calling getSubmitBlockers itself. Without both, the rail showed Compute complete over a preflight that refused to submit.
  • Every shouldComponentUpdate addition 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.
  • The demo needed real data before any of this could be reviewed. It passed 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 in review/, each with a Status section recording what shipped, where it diverged, and what is still open. The overview keeps what outlives them.
  • AGENTS.md — from mat3ra/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.ts duplicates @mat3ra/ive's utils/computeEstimate, which is canonical. The copy exists only because the ive release carrying it hasn't shipped; it has a TODO(SOF-8023) to delete it. The two are identical today.

Companion PRs — merge order

  1. Design language: status tones, StatusChip, CopyId, palette fixes, and four new primitives cove#97 — palette, StatusChip/CopyId, and six new primitives
  2. Hide flowchart ids and designer status by default; let the flowchart follow its theme wove#11 — ids/status opt-in, flowchart theming
  3. Compute form: progressive validation, cluster cards, presets and a live estimate ive#6, Let the host hide a subworkflow's Compute tab workflow-designer#13fix: dispatch material removal to the designer's own store #14, Run monitor: show what a submitted job is doing jove#9 — all import cove primitives that don't exist in the published package, so step 1 is a hard prerequisite
  4. this PR — bump the pins, drop the duplicated estimator, then merge

Verification

tsc clean, eslint 0 errors, 86 unit tests pass. Behaviour was measured in the running demos, not assumed:

  • draft → 4 steps remaining, Submit disabled naming the first;
  • compute filled → Ready to submit, estimate chip 64 core·h ≈ $5.12, preflight 5 passes;
  • walltime 24 h → Compute reads over the 12 h queue limit, Submit disabled with the reason in its tooltip;
  • 4 × 32 × 12 h → within limits but fails budget at 1536 of 500 core·h; "Reduce resources" lands on Compute;
  • a warning holds Submit until acknowledged, then releases it;
  • job set running → lifecycle 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;
  • guided layout off → rail and strip gone, numbered tabs back, one-click submit unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8

claude added 7 commits August 15, 2026 21:14
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
@timurbazhirov timurbazhirov changed the title SOF-8023: Job Designer UX brainstorm — proposals, mockups, and guided-designer plan SOF-8023: Job Designer UX — plan, mockups, and phase 1 (header Submit, error state, single Compute tab) Aug 16, 2026
claude added 8 commits August 16, 2026 20:29
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
@timurbazhirov timurbazhirov changed the title SOF-8023: Job Designer UX — plan, mockups, and phase 1 (header Submit, error state, single Compute tab) SOF-8023: Job Designer UX — plan, mockups, phase 1, and phase 2.5/2.6 Aug 16, 2026
claude added 5 commits August 16, 2026 23:05
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
@timurbazhirov timurbazhirov changed the title SOF-8023: Job Designer UX — plan, mockups, phase 1, and phase 2.5/2.6 SOF-8023: Job Designer UX — plan, mockups, phase 1 and phase 2 Aug 17, 2026
claude added 4 commits August 17, 2026 03:27
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
@timurbazhirov timurbazhirov changed the title SOF-8023: Job Designer UX — plan, mockups, phase 1 and phase 2 SOF-8023: Job Designer UX — plan, mockups, and phases 1–3 Aug 17, 2026
claude added 2 commits August 17, 2026 07:07
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

Copy link
Copy Markdown
Member Author

Cross-cutting items now done too

The 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 guardrail

The plan asked for "a regression test that counts job.render() calls during compute edits". tests/renderGuardrail.tests.ts does exactly that, because the guided designer recomputes readiness, the estimate and the submit blockers on every render — which means on every keystroke in the compute form. If any of them touched the entity, typing a walltime would re-render the whole workflow.

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.

Localization

The 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. src/messages.ts now holds each with a key and an English default; hosts inject 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, 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: "Configure compute" existed twice. Merged.

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.

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 event set rather than scattering string literals: designer opened + 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. Still needed: the webapp's recorder and a dashboard. The events exist; nothing collects them yet.

e2e

The existing suite tested a placeholder app: #job-designer-app, an h1, #status-badges-container and four status badges — none of which exist in the demo, so 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.

Replaced with guided_designer.feature, covering the plan's list. Selectors are ids the components set deliberately, never text or classes, so a copy change — or a translation — fails the assertion it should rather than the lookup.

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 aria-label reading "Configure compute" and all five preflight rows. The step logic is exercised; the Cypress harness itself is unverified here and wants a run before anyone relies on it.

And in cove

The risk table's "ButtonMultiSelect mount-snapshot bug class" mitigation asked for a regression test. mat3ra/cove#97 now has one: the resolution rule is a pure module with 8 assertions covering what the object-holding version got wrong. Job.jsx's read-at-click-time workaround can come out once cove ships.


Generated by Claude Code

claude added 2 commits August 17, 2026 07:55
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

Copy link
Copy Markdown
Member Author

I audited the acceptance criteria against the running app, and two of them were not met

I 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 false

The 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 false

The 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 md breakpoint.

viewport before after
1400 px 284×262, vertical 320×262, vertical
900 px 284×262, vertical 320×262, vertical
700 px 700×263, vertical 700×77, horizontal
500 px 500×263, vertical 500×77, horizontal

The affordances exposed a crash

Clicking Change threw object is not iterable. This package's types disagree with its own code: JobDesignerDialogState describes { isOpen, open, close } — what the container declares and the demo passed — while Job.jsx destructures [open, close], which is what useReduxDialog returns and therefore what the webapp actually flows. Both shapes are real.

Nothing caught it because the only route to those openers was the actions dropdown, which no test or demo run had ever clicked. normalizeDialogHandle now accepts either rather than picking a winner, so no host has to migrate first, and a dialog the host never wired does nothing instead of throwing. 5 unit tests.

What held up

2.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 savedUnsaved changes on an edit with the beforeunload guard arming alongside it (both directions checked by dispatching the event and reading defaultPrevented).

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 decision

A saved draft cannot change its materials. MaterialTab gets addRemoveAllowed={!job.id}, so the tray's Add and Remove disappear the moment a draft is saved — while editable (status === pre_submission) stays true, so the designer simultaneously reports the job as editable.

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 _id so Submit is not permanently blocked on "Save the job", which switches the remove affordance off.

The rail's Change Material is gated on isInInitialStatus instead, so the selector stays reachable either way.


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
@netlify

netlify Bot commented Aug 23, 2026

Copy link
Copy Markdown

Deploy Preview for mat3ra-job-designer ready!

Name Link
🔨 Latest commit 8cbd58a
🔍 Latest deploy log https://app.netlify.com/projects/mat3ra-job-designer/deploys/6a8b7811145ad80008cb1d21
😎 Deploy Preview https://deploy-preview-19--mat3ra-job-designer.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

claude added 2 commits August 23, 2026 01:53
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

Copy link
Copy Markdown
Member Author

Netlify deploy preview: wired up, and red for one reason

The mat3ra-job-designer site is now configured from the repo (netlify.toml), but its check will stay red until cove publishes the new primitives. Recording the diagnosis so the red check isn't mistaken for a defect in this branch.

What was actually wrong

Three separate faults, found in order. The first two were real and are fixed here:

  1. npm ci could not run at allEUSAGE ... Missing: husky@7.0.4 from lock file. dfd2501 added husky to devDependencies without regenerating the lockfile. Nothing caught it because this repo's CI and README install with npm install --legacy-peer-deps, which reconciles the drift; Netlify installs with npm ci, which refuses to. Note this is EUSAGE, not ERESOLVE--legacy-peer-deps does not rescue it.

  2. A lockfile entry required an SSH key"resolved": "git+ssh://git@github.com/Exabyte-io/simpl-schema.git#355fbea", present on main too. Netlify's builder has no GitHub SSH key. This is invisible locally because our containers and CI carry a git insteadOf rule rewriting ssh://git@github.com/ to https://github.com/. Neither package that pulls simpl-schema in asked for SSH — @mat3ra/workflow-designer (dependency) and @mat3ra/move (peer) both declare git+https://; npm rewrote it when writing resolved. Rewritten back; the lockfile now holds no git+ssh entries.

  3. The blocker — the build cannot resolve @mat3ra/cove/dist/mui/components/lifecycle/LifecycleTimeline. Both this repo and the lockfile pin cove 2026.7.28-0, and the published 2026.7.28-0 does not contain the Phase 1.5 primitives. Same root cause as the red CI on ive chore: consume wave.js from @mat3ra scope #6 and jove fix: forward fileUtils and DataGridComponent to ResultsTab #9.

Correcting an earlier claim

I previously reported that this branch builds cleanly with npm run build:standalone. That was true only in my working container, whose node_modules/@mat3ra/cove had been replaced with a locally-built copy during the cove work. Against the published package it does not build. Verified by reproducing in a clean clone.

Evidence that cove is the only remaining blocker

In a clean clone (npm ci, published versions of every @mat3ra package), swapping in the locally-built cove dist and nothing else:

✓ built in 1m 46s     EXIT=0

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

netlify.toml sets VITE_BASE=/ because Netlify serves from the domain root while GitHub Pages serves from /job-designer/. Vite's base now reads that env var, defaulting to the Pages subpath, so Pages and npm run dev are unchanged — verified both ways. A wrong base here fails as a blank page rather than a build error, which is why it is worth stating.

Two things I did not determine: the cause of the very first production deploy failure from main at 01:29 UTC, which predates any of this and ran before main had a netlify.toml; and anything from Netlify's build logs, which need account credentials this session doesn't have — the whole diagnosis above comes from local reproduction plus the unauthenticated deploy API.


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

Copy link
Copy Markdown
Member Author

Correction, and merged main in

Retracting one claim from my previous comment

I wrote that the git+ssh://…/simpl-schema lockfile entry broke Netlify's install. That was wrong. npm falls back to an HTTPS tarball for public GitHub repos, and I verified simpl-schema installs cleanly with a cold cache and this container's ssh→https rewrite disabled. The entry was never a problem, and the commit that rewrote it fixed nothing. This merge takes main's lockfile wholesale, which reverts it.

Fault 1 in that comment was real: npm ci genuinely could not run against a lockfile missing husky.

Merging main (conflicts resolved)

main reversed two things this branch was built on, so the resolution wasn't 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. AGENTS.md §1.7.1 documented the opposite policy at length, so it's rewritten rather than left to rot: this repo lets CI build and publish, while the rest of the @mat3ra family hasn't all migrated, so it now says to check git ls-tree per repo instead of assuming either model.

Pins moved to published versions (3983b74). Took main's versions 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.
  • husky and "prepare": "husky install" are dropped. They existed only to keep the tracked dist/ in sync, and that reason is gone. Arming main's current hook actively breaks commits: its first line is npx lint-staged, and lint-staged is neither a dependency nor configured anywhere here. Not a prediction — my first attempt at the merge commit was rejected by that hook, which fetched lint-staged@17.3.0 and failed with could not find any valid configuration. AGENTS.md now records why the hook is dormant and what arming it would require.

The diff drops from 134 files to 62 and from ~10.6k additions to ~7.4k, entirely by shedding dist/.

Verified after resolution: tsc clean, eslint 0 errors, 111/111 tests, 0 files tracked under dist/, and main's ApplicationDriverStandataDriver rename in src/standalone/preloads.ts intact.

Still blocked on cove

@mat3ra/cove@2026.8.19-4 — the version main now pins — ships none of the six primitives from cove#97. Its dist/mui/components/ contains only the pre-existing set (accordion, button, checkbox, custom, dialog, dropdown, icon, linear-progress, nested-dropdown, popover, radio-group, select, stepper, tabs). So build:standalone and the Netlify deploy preview stay red until cove#97 is released. The merge order in the description is unchanged.

Worth knowing why unit CI stays green while the bundle doesn't build: tsc doesn't typecheck Job.jsx, which is where the cove import lives. Only the Vite build resolves it.


Generated by Claude Code

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

Copy link
Copy Markdown
Member Author

Deploy preview is live

https://deploy-preview-19--mat3ra-job-designer.netlify.app

No longer blocked on cove#97 merging. Per mat3ra/agents#5, cove's release-wip workflow publishes a branch's build as a GitHub pre-release tarball, so this branch installs cove#97 directly:

"@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. build:standalone completes in 1m30s where it previously died in 9s.

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 mistakes

Recording them because they're the same failure class and cheap to repeat:

  1. npm ci EUSAGE. After merging main into cove#97, I reconciled its auto-merged lockfile with npm install --legacy-peer-deps. cove doesn't need that flag and its CI doesn't use it — legacy peer resolution produced a tree only my local install satisfied.
  2. tsc failed on a dependency. Fixing that with npm install --package-lock-only re-resolved from scratch and floated @types/node from main's 22.10.7 to 26.2.0, whose .d.ts syntax cove's TypeScript can't parse: ffi.d.ts(273,35): error TS1005.

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 package.json change is the test/test:unit scripts. So it needs no lockfile of its own; restoring main's byte-for-byte was correct, and the release then published first try.

Verified, not assumed

  • cove#97: npm ci exits 0, npm run transpile exits 0, npm test green (112/112) — checked locally before pushing, after burning two CI runs guessing.
  • job-designer: npm ci --dry-run resolves (Netlify installs with npm ci, so a lockfile it rejects fails the deploy before building), tsc clean, eslint 0 errors, prettier clean, 111/111 tests.
  • The deploy itself: / returns 200 referencing /main.js at the root, main.js 200 / 14.1 MB, and a deep path returns 200 through the SPA redirect. I then downloaded exactly what Netlify serves, rendered it, and confirmed the full guided designer — rail with Change affordances, context strip, lifecycle timeline, save state, metadata panel — rather than trusting that a green build means a working page.

What the preview does and doesn't show

It 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

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.

2 participants