Skip to content

feat(machines): Machine Settings tab — owner-defined settings sets (PP-43q3)#1388

Open
timothyfroehlich wants to merge 49 commits into
mainfrom
feat/machine-settings-tab-scaffold-PP-43q3
Open

feat(machines): Machine Settings tab — owner-defined settings sets (PP-43q3)#1388
timothyfroehlich wants to merge 49 commits into
mainfrom
feat/machine-settings-tab-scaffold-PP-43q3

Conversation

@timothyfroehlich

@timothyfroehlich timothyfroehlich commented May 19, 2026

Copy link
Copy Markdown
Owner

Implements the Machine Settings tab end-to-end (PP-43q3): owner-defined settings sets (software settings + baseline, DIP switch banks, rubbers/post-positions/notes), with an exclusive Preferred set, persisted and permission-gated, and surfaced on the machine timeline.

Built on the merged precursor #1492 (PP-8oy0, DEFAULT_TIMELINE_TAGS query seam) and hardened by a 6-lens adversarial plan review.

What's in it

  • Schema (0038): machine_settings_sets — one ordered sections JSONB array (mixed-kind discriminated union), JSONB description, is_preferred, created/updated by+at; partial unique index WHERE is_preferred (one preferred per machine); RLS enabled.
  • Permissions: machines.settings.manage (member→owner, technician/admin→any; viewing public via machines.view).
  • Server actions: save (upsert, IDOR guard, no-op deep-compare), delete, duplicate, exclusive setPreferred — with runtime Zod validation of the untrusted payload (the $type<> is compile-time only) and size caps.
  • Client: real-data page; save-on-Done with temp-id→UUID reconciliation; unsaved-changes guard (beforeunload + soft-nav confirm); baselineNote field; mentions off; tab order Info · Settings · Service · Timeline.
  • Timeline: settings_set_* events under a new settings tag, default-OFF in the filter (and the Info-tab recent activity), toggleable on; filter chips materialize the effective default set so toggling settings adds to — rather than narrows to — the view.

Tests

  • Integration (PGlite): CRUD round-trips, the 23505 partial-index backstop, no-op guard, malformed-payload rejection, full permission matrix, and the timeline-emit assertions.
  • Unit: permission matrix/helper cases; tag-list/reserved/userTagSchema updates.
  • Smoke: /m/[initials]/settings added to responsive-overflow.
  • E2E: create → Done → reload → persists, tab nav, non-owner read-only.

Local: check (1214) + full integration (272+106) + production build all green; settings/timeline E2E green. (preflight's smoke step is blocked by the unrelated infra bug PP-yso5 — heavy-run.sh/sem word-splits --project='Mobile Chrome'.)

Adds a new Settings tab to the machine detail page with hardcoded sample
data. UI-only scaffold — no schema, no server actions, no persistence.
Interactions (expand/collapse, star preferred, baseline select, inline
edit open) are all wired client-side with no-op handlers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 19, 2026 13:34
@vercel

vercel Bot commented May 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pin-point Ready Ready Preview, Comment Jun 18, 2026 2:51am

Request Review

@supabase

supabase Bot commented May 19, 2026

Copy link
Copy Markdown

Updates to Preview Branch (feat/machine-settings-tab-scaffold-PP-43q3) ↗︎

Deployments Status Updated
Database Tue, 19 May 2026 13:35:11 UTC
Services Tue, 19 May 2026 13:35:11 UTC
APIs Tue, 19 May 2026 13:35:11 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Tue, 19 May 2026 13:35:19 UTC
Migrations Tue, 19 May 2026 13:35:24 UTC
Seeding Tue, 19 May 2026 13:35:24 UTC
Edge Functions Tue, 19 May 2026 13:35:24 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Scaffolds a UI-only "Settings" tab for the machine detail page with hardcoded sample data — adds the route, the tab strip entry, and a set of presentational client components (set cards, markdown sections, software settings table, dip switch table, baseline selector). No schema, server actions, or permission wiring yet; canEdit is hardcoded true and all save/CTA handlers are no-ops.

Changes:

  • New route /m/[initials]/settings and Settings entry in MachineTabStrip.
  • New src/components/machines/settings/ directory with SettingsTab, SettingsSetCard, MarkdownSection, SoftwareSettingsSection, DipSwitchSection, BaselineSelect.
  • Uses semantic Material Design 3 tokens (warning, success, outline-variant) rather than raw palette classes.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/app/(app)/m/[initials]/(tabs)/settings/page.tsx New server-component route that resolves the machine and renders SettingsTab with canEdit={true}.
src/components/machines/MachineTabStrip.tsx Adds the settings tab spec after maintenance.
src/components/machines/settings/SettingsTab.tsx Client wrapper holding sample-data state, expand-set, and exclusive preferred-set toggle.
src/components/machines/settings/SettingsSetCard.tsx Expandable card with header (chevron/star/badge/kebab) and sectioned body.
src/components/machines/settings/MarkdownSection.tsx Thin wrapper over InlineEditableField with a no-op save.
src/components/machines/settings/SoftwareSettingsSection.tsx Table of software settings plus an inline baseline editor.
src/components/machines/settings/DipSwitchSection.tsx Table of dip switches with ON/OFF pill styling.
src/components/machines/settings/BaselineSelect.tsx Grouped Select with custom free-text branch via Other....

Comment thread src/components/machines/settings/SettingsSetCard.tsx Outdated
Comment thread src/app/(app)/m/[initials]/(tabs)/settings/page.tsx
Comment thread src/components/machines/settings/SettingsSetCard.tsx Outdated
CORE-RESP-003 (the new audit:sm-structural lint added in PP-kqbk.6)
caught a sm:block on the collapsed-meta string in SettingsSetCard.

The card is a self-contained component — its layout decisions should
be driven by its own width, not the viewport. Added @container on
the header row and switched the meta string from sm:block to @md:block.

PP-43q3
…43q3)

Layer-1 statefulness (client-side only, no schema or server actions yet):
- Set kebab → Duplicate now clones with " (copy)" suffix
- Set kebab → Delete confirms then removes
- "+ New set" appends a blank set, auto-expanded
- Star toggle exclusivity preserved
- Click-to-edit set name via new InlineEditableText (commit on
  blur/Enter, revert on Esc) — primes the same pattern Layer 2 will
  use for table cells
- Baseline picker change persists across collapse/expand
- Markdown surfaces (description / rubbers / post positions / notes)
  now persist their edits to local state via onValueChange callbacks

Header reshape per design feedback:
- Description preview is now always visible, lives in the header
  (click-to-edit there, removed from the expanded body)
- "updated by X DATE" rendered inline next to the set name; the
  separate meta strip ("4 software settings · 3 dip switches") is
  gone
- Section counters ("4 settings", "3 switches") dropped from
  SoftwareSettings + DipSwitch headers

Dip switches restructured to group by bank:
- New shape: dipSwitchBanks = [{ id, name, switches: [...] }]
- Each bank renders as a native <details>/<summary> Accordion item
  (default-open) with the switches as a small table inside
- "+ Add switch" inside each bank + "+ Add bank" below all banks
  (UI affordances only — handlers deferred to Layer 2)

Data-shape changes (still scaffold-only):
- description/rubbers/postPositions/notes: string → ProseMirrorDoc | null
- baseline: { group, value } → encoded "Group__Value" string (or
  "Other..." / "custom__free-text")
- dipSwitches: flat array → dipSwitchBanks grouped array

Deferred to next pass:
- Inline cell editing for software-settings rows + dip-switch rows
  (Layer 2 — blur-save policy still parked at PP-s9xi)
- Wired Add row / Add switch / Add bank handlers

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.

Comment thread src/components/machines/settings/SettingsTab.tsx Outdated
Comment thread src/components/machines/settings/SettingsSetCard.tsx Outdated
Comment thread src/components/machines/settings/SettingsSetCard.tsx Outdated
Comment thread src/components/machines/settings/BaselineSelect.tsx Outdated
Comment thread src/components/machines/settings/BaselineSelect.tsx Outdated
Comment thread src/app/(app)/m/[initials]/(tabs)/settings/page.tsx
Comment thread src/components/machines/settings/SoftwareSettingsSection.tsx Outdated
Three asks rolled into one tweak to the description in the header:
- Drop the "DESCRIPTION" label above the preview
- Smaller font (text-xs) on both the displayed text and the editor
- Clicking anywhere on the displayed text enters edit mode
  (clicks on links inside the rendered markdown still navigate
  to the link rather than entering edit mode)

Built a focused DescriptionInline component rather than extending
the shared InlineEditableField — InlineEditableField intentionally
keeps its pencil as the sole edit trigger so its content (which can
include <a> mentions/links) stays cleanly clickable. The Settings
card's header description doesn't need to share that behavior, and
diverging the click semantics on the shared component would silently
change the four other call sites on the Info tab.
…ker (PP-43q3)

Hardware-section visibility (Option A from session discussion):
- SoftwareSettingsSection auto-hides when softwareSettings is empty
- DipSwitchSection auto-hides when dipSwitchBanks is empty
- Both empty → new HardwareAdjustmentPicker renders with dropdown
  ("Software setting" / "DIP switch") + a one-liner explaining the
  most-machines-have-one-or-the-other rationale
- One missing → direct-button picker ("Add software settings" /
  "Add DIP switches")
- Both present → no picker
- Sample data updated to demonstrate all three states: Standard House
  (software only), Friday Tournament (software only), new
  "Black Knight (early SS demo)" (DIP only, two banks)

Cell-level inline editing (blur-save per PP-s9xi lean):
- New EditableCell primitive: click cell → Input → blur or Enter
  commits (calls onCommit only on change), Esc reverts. Tab through
  works (browser default — buttons + inputs are focusable). Newly
  added rows mount in edit mode with the first cell focused via the
  autoFocusOnMount prop.
- Software-settings rows now use EditableCell for ID / Setting / Value.
- DIP-switch entries use EditableCell for Switch and Note. Position is
  a binary toggle pill (click to flip ON↔OFF) — no edit mode needed.

Row + bank management:
- "+ Add row" appends a blank software row and focuses its ID cell.
- "+ Add switch" inside a bank appends a blank entry and focuses it.
- "+ Add bank" creates a new bank with auto-name "Bank N" + one blank
  switch, focused.
- Trash icon on row hover deletes that row/switch.
- Each bank gets a "Delete bank" button at the bottom of its content
  with a confirm dialog.

Sample data now includes _key fields on every row/switch so React
keys stay stable across edits. Duplicate-set also deep-clones rows
with fresh _key values so editing the copy doesn't mutate the original.

Bank rename is deliberately left out of this commit — putting a
click-to-edit button inside the AccordionTrigger (a <summary>) is
fiddly because of the native toggle behavior. It can land in a small
follow-up if you want it before merge.

Layer 1 (header reshape, kebab Duplicate/Delete, click-to-edit title
and description, "+ New set", baseline + markdown persistence) all
unchanged from the prior commit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Comment thread src/components/machines/settings/SettingsTab.tsx
Comment thread src/components/machines/settings/SoftwareSettingsSection.tsx Outdated
Comment thread src/components/machines/settings/DipSwitchSection.tsx Outdated
Comment thread src/components/machines/settings/DescriptionInline.tsx Outdated
Comment thread src/app/(app)/m/[initials]/(tabs)/settings/page.tsx
…3q3)

Edit-mode gate (replaces always-on click-to-edit):
- SettingsTab gains an isEditMode toggle. A top-of-tab Edit button
  (shown only when canEdit — the owner/tech+ permission, still
  hardcoded true at the page for the scaffold) flips view ↔ edit.
- Entering edit mode expands every set and keeps them expanded.
- In edit mode the button becomes "Done"; the "New set" button only
  shows while editing.
- All mutation affordances are gated on the effective edit state
  (canEdit && isEditMode), threaded down as each card's canEdit:
    - Set name / description / cells / baseline: read-only in view mode
    - Star (Preferred): interactive button in edit mode; in view mode
      it's a static gold indicator rendered only on the preferred set
    - Kebab (Duplicate/Delete): hidden in view mode
    - Add row / Add switch / Add bank / trash / picker: hidden in view
      mode (they already keyed off canEdit)
- Empty-state copy adapts: "Click Edit above to add one" in view mode,
  "Click New set above" in edit mode, plain text with no permission.

Bank rename:
- DipSwitchSection swapped from the native <details>/<summary>
  accordion to a manual one (chevron button owns open/closed state).
  This frees the bank name to be an InlineEditableText beside the
  chevron — a <summary> toggles on any inner click as a default
  action that stopPropagation can't cancel, so the native accordion
  couldn't host a click-to-edit name. New onRenameBank handler in
  SettingsTab.

Label:
- "Starting from:" → "Initial Install:" on the software-settings
  baseline strip.

Not in this commit (pending your call):
- "Other"/baseline picker redesign — bringing datalist vs combobox
  options for you to choose.
Per-set edit mode (replaces the global edit toggle):
- Each set card has its own Edit / Done button. Entering edit mode
  expands that set and unlocks its content affordances only.
- Set-level operations are always available when canEdit (the
  owner/tech+ permission), regardless of edit mode:
    - Kebab (Duplicate / Delete)
    - Preferred star
    - "New set" button (back in the toolbar unconditionally)
- Content editing (name, description, software/dip cells, baseline,
  add/delete rows, hardware picker) gates on canEdit && isEditing,
  threaded to children as `contentEditable`.
- SettingsTab tracks editingIds (a Set) instead of a single global
  isEditMode boolean.

Baseline → shadcn combobox (your pick over native datalist):
- New BaselineCombobox (Popover + cmdk) with Stern / Bally-Williams
  group headings, a "Use '<typed>'" custom row, and a persistent
  footer hint ("Don't see your install? Just type it in — any text
  works") plus a "Pick a preset, or type your own…" search
  placeholder to make custom entry obvious.
- baseline is now a plain string (e.g. "Competition Install"); dropped
  the old Group__Value / custom__ / "Other..." encoding entirely.
- SoftwareSettingsSection shows the combobox in edit mode and plain
  text ("…or 'Not specified'") in view mode.
- Deleted BaselineSelect.tsx (old encoded Select) and
  BaselineDatalist.tsx (the comparison loser); removed the temporary
  side-by-side comparison block from SettingsTab.

Label: "Starting from:" stays "Initial Install:".

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.

Comment thread src/components/machines/settings/SettingsTab.tsx Outdated
Comment thread src/app/(app)/m/[initials]/(tabs)/settings/page.tsx
Comment thread src/app/(app)/m/[initials]/(tabs)/settings/page.tsx
Comment thread src/components/machines/settings/SoftwareSettingsSection.tsx Outdated
Comment thread src/components/machines/settings/DipSwitchSection.tsx Outdated
Comment thread src/components/machines/settings/DescriptionInline.tsx Outdated
Comment thread src/components/machines/settings/SettingsTab.tsx Outdated
Comment thread src/components/machines/settings/DescriptionInline.tsx Outdated
timothyfroehlich added a commit that referenced this pull request Jun 13, 2026
Counter-evidence from #1388 (run 27481880209 cold-fail, 27482214047 warm-pass on
the SAME :6543 url) shows migrate failure is a cold-start RACE on freshly
provisioned branches, not a hard DDL-pooler incompatibility. Add a bounded retry
(4 attempts, 10s backoff) around drizzle-kit migrate; migrations are journaled +
per-migration transactional so a retry re-applies cleanly. Keeps the tr error
visibility and the session-pooler endpoint as defense-in-depth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
timothyfroehlich added a commit that referenced this pull request Jun 14, 2026
…-9opq)

The sticky preview URL was string-templated as
pin-point-git-<branch>-advacar.vercel.app. When that label exceeds the 63-char
DNS limit (e.g. PR #1388's branch -> 64 chars), Vercel hash-truncates the real
alias, so the templated host does not resolve. Capture the alias Vercel actually
assigned from the deployment API in wait_for_ready (.alias[] '-git-' entry, else
.meta.branchAlias) and use it for PREVIEW_URL, falling back to the template only
when the API alias is unavailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
timothyfroehlich added a commit that referenced this pull request Jun 14, 2026
…cel alias (PP-l9qb, PP-9opq) (#1538)

* fix(ci): run preview migrations via session pooler + surface migrate errors (PP-l9qb)

The /preview create step ran 'drizzle-kit migrate' through POSTGRES_URL — the
:6543 transaction pooler — which does not reliably support the DDL/prepared
statements a migration issues (drizzle.config.ts warns about exactly this). It
failed with exit 1 and no surfaced Postgres error on every run; no preview was
ever produced.

- Route migrations through the SESSION-mode pooler (same host, :5432, IPv4 +
  DDL-capable) by swapping the port on POSTGRES_URL for POSTGRES_URL_NON_POOLING.
- Pipe migrate through 'tr \\r \\n' so drizzle-kit's spinner can no longer bury
  the real Postgres error behind carriage-return overwrites in the CI log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ci): retry preview migrate to clear cold-start race (PP-l9qb)

Counter-evidence from #1388 (run 27481880209 cold-fail, 27482214047 warm-pass on
the SAME :6543 url) shows migrate failure is a cold-start RACE on freshly
provisioned branches, not a hard DDL-pooler incompatibility. Add a bounded retry
(4 attempts, 10s backoff) around drizzle-kit migrate; migrations are journaled +
per-migration transactional so a retry re-applies cleanly. Keeps the tr error
visibility and the session-pooler endpoint as defense-in-depth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ci): post Vercel-assigned preview alias, not a templated host (PP-9opq)

The sticky preview URL was string-templated as
pin-point-git-<branch>-advacar.vercel.app. When that label exceeds the 63-char
DNS limit (e.g. PR #1388's branch -> 64 chars), Vercel hash-truncates the real
alias, so the templated host does not resolve. Capture the alias Vercel actually
assigned from the deployment API in wait_for_ready (.alias[] '-git-' entry, else
.meta.branchAlias) and use it for PREVIEW_URL, falling back to the template only
when the API alias is unavailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
timothyfroehlich and others added 2 commits June 13, 2026 21:44
…3q3)

Wire seed-machine-settings.mjs into preview-create.sh so the on-demand
preview pipeline populates the two AFM showcase settings sets. Drop the
localhost-only guard from the seed (it blocked legit ephemeral-branch
seeding and was inconsistent with the unguarded seed-users.mjs); the
read-only root checkout + AGENTS.md rules already forbid pointing a seed
at prod. Follow-up to add a consistent ref-based guard: PP-0abt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@timothyfroehlich

Copy link
Copy Markdown
Owner Author

/preview

Add a machine-level rich-text "How to change settings" block at the top of
the Settings tab (coin-door buttons, reset procedure, DIP-switch locations,
etc.), shared by every settings set. Stored as a new machines.settings_instructions
column and edited inline via the existing InlineEditableField, gated by
machines.settings.manage (no timeline event).

Removes the per-software-section baselineNote field it replaces (keeps the
baseline/"Initial install" picker). Updates the help page, the AFM seed, and
the unit/integration tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@timothyfroehlich

Copy link
Copy Markdown
Owner Author

/preview extend

…-tab-scaffold-PP-43q3

# Conflicts:
#	drizzle/meta/0042_snapshot.json
#	drizzle/meta/_journal.json
@timothyfroehlich

Copy link
Copy Markdown
Owner Author

/preview

timothyfroehlich and others added 2 commits June 16, 2026 20:49
…PP-43q3)

Rework the Machine Settings tab from per-set Edit→Done autosave to an explicit
per-unit edit model:

- Each (title+content) unit — the set header and each section — has its own
  Edit / Save / Cancel. Field edits buffer in a working copy and commit
  atomically per unit: Save merges only that unit's slice onto the committed
  baseline, so parallel drafts stay isolated (saving one unit never persists
  another's open edits). Cancel reverts the slice from baseline.
- Structural ops (delete, reorder) persist immediately from baseline while
  preserving other open drafts; add-section creates a draft that commits on its
  Save; a brand-new set inserts on first Save (temp-id → server-uuid swap).
- Per-set serial save queue with out-of-order/clobber protection and temp→UUID
  rekey; navigation guard scoped to "any unit dirty".
- Affordances: full "Edit" button on the header, small icon-only pencils on
  sections, opaque boxed single-line inputs, rich bodies open straight into the
  full editor (no mini/full toggle), full-cell desktop click target for table
  cells, per-section kebab (Delete / Move up / Move down) + desktop-only grip.

Removes the old per-field autosave + FieldSaveStatus and the set-level dirty
serializer (settings-dirty). jsonb schema and saveSettingsSetAction unchanged.

Also bundles in-progress PP-5r0p presets for the machine-level "How to change
settings" field (settings-instructions-presets + InlineEditableField wiring),
which shares SettingsTab.tsx and could not be committed separately.

Checkpoint commit on the feature branch; preflight + unit/integration tests
green. Not yet split for review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Conflicts resolved:
- Migration meta: regenerated our settings migration on top of main's
  0043_productive_cyclops (#1554 tournament-notes removal). The settings table
  + machines.settings_instructions column is now 0044_chunky_vermin; took
  main's drizzle/meta and deleted the orphaned 0043_spooky_stone_men.sql.
  db:reset applies the full chain clean; db:generate reports no schema changes.
- MachineTabStrip.tsx: adopted main's new <RouteTabStrip> component and added
  our "Settings" tab (Info / Settings / Service / Timeline).
- machine-event-icons.ts: kept Trash2 (settings_set_deleted icon), dropped
  Trophy (tournament icon removed upstream, no longer referenced).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
timothyfroehlich added a commit that referenced this pull request Jun 17, 2026
… on reuse (#1553)

Previews served stale schema/seed because `/preview` only ran migrate+seed at
comment time and reused a live Supabase branch without resetting it. drizzle
migrate skips already-journaled migrations and the seed scripts skip existing
rows, so a regenerated migration looked "not applied" and changed seed data
never updated (PR #1388). Later pushes never re-ran migrate/seed at all.

- Add preview-sync.yaml: on `pull_request` synchronize, path-filtered to
  drizzle/** + supabase/seed*, re-sync an already-active branch. Exits 0 when no
  `pr-<N>` branch exists, preserving the zero-cost "no preview by default" model.
- Extract the migrate+seed sequence into the shared preview-migrate-seed.sh so
  the `/preview` and push-resync paths can never drift; PREVIEW_RESET=1 resets
  before migrate when reusing a live branch.
- preview-create.sh resets on the reuse path (manual restart now applies changes).
- reset-preview-db.mjs also clears seeded auth.users (seed-users is skip-if-exists
  and the profile trigger only fires on INSERT, so a public-schema-only reset left
  broken login state) + a PROD_PROJECT_REF safety guard.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
timothyfroehlich and others added 3 commits June 16, 2026 21:31
…PP-43q3)

Add unit + integration + E2E tests for the per-unit edit / atomic-save model:
- save-queue concurrency (coalesce, temp→uuid rekey, error + thrown executor) —
  new hook unit test
- SettingsTab RTL: cancel discards a never-saved set / draft section,
  required-name and required-title guards block Save, nav-guard arm/disarm +
  confirm-on-dirty-anchor-click, structural op on a never-saved set is local-only
- InlineEditableText / EditableCell: enterkeyhint, Esc-revert, required-error
  show/clear, codeLike autocorrect/autocapitalize/spellcheck off
- integration: 200KB byte-ceiling rejection, duplicate NAME_MAX truncation
- E2E: delete-section and reorder survive reload; realign the stale
  "Done"→"Save" editor-journey assertion the rework outdated

No production changes — every component held up under the new tests; only a
stale test was realigned to the per-unit Save UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-tab-scaffold-PP-43q3

# Conflicts:
#	AGENTS.md
…P-43q3, PP-8a5r)

PP-8a5r: machine-level "Before you change anything" owner-requests field
(settings_requests jsonb column + migration, server action, seed, card wiring).

Review remediation (PR #1388 high-effort review), each with a regression test:
- fix(A1, data loss): a section saved while a brand-new set's insert is still
  in flight is no longer dropped — baseline the new set from the sent slice and
  carry any newer staged payload to the real id.
- B1: unsaved-changes nav guard now covers the two always-open machine-level
  fields (they report dirtiness up via onDirtyChange).
- B3/Cl2/Cl5: optimistic value represents clears and reconciles with the server
  value; isDirty memo via shared docsEqualByText.
- A2/I1: optional inline fields can be cleared; required fields show an asterisk
  placeholder + aria-required.
- I2: editable-cell placeholder meets WCAG 1.4.3 contrast.
- I3: drop misused title tooltip on the two-tap delete (CORE-A11Y-005).
- A4: local-date today(); A3: remove dead save-queue cleanup branch.
- Cl3/Cl4: shared guarded ProseMirror docIsEmpty/docsEqualByText helpers.
- P3 a11y: edit pencil visible on touch ([@media(hover:none)]); read-only
  "Add row" uses inert instead of pointer-events-none + tabIndex + aria-hidden.

+12 unit/integration regression tests (1296 -> 1308). Follow-ups beaded:
PP-cumd (same-set multi-unit coalescing race), PP-annw (variant collapse).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@timothyfroehlich

Copy link
Copy Markdown
Owner Author

/preview

The lockfile carried a stale react@19.2.6 reference after the origin/main
merge bumped react to 19.2.7, breaking CI's `pnpm install --frozen-lockfile`
(ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY) and skipping all downstream test jobs.
Regenerated with `pnpm install`; verified `--frozen-lockfile` now resolves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@timothyfroehlich

Copy link
Copy Markdown
Owner Author

/preview

1 similar comment
@timothyfroehlich

Copy link
Copy Markdown
Owner Author

/preview

The preview pipeline's 'Seed machine settings demo' step crashed with
ENETUNREACH because seed-machine-settings.mjs preferred
POSTGRES_URL_NON_POOLING (:5432), which resolves to IPv6 and is
unreachable from CI/preview runners. Switch to the pooled POSTGRES_URL
(:6543 IPv4) like seed-users.mjs / seed-discord.mjs, per AGENTS.md §7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@timothyfroehlich

Copy link
Copy Markdown
Owner Author

/preview

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