Human overview · for understanding

Comment-editor rebuild — the plan

One mutable working copy · publish makes the version · full undo · faster by design · 2026-06-23

One mutable working copy · publish makes the version · full undo · faster by design

Master summary — the gist in 30 seconds

TL;DRStop minting a new frozen version on every little edit. Instead the admin edits ONE living working copy, and a version is born only when they hit 'Ügyfélnek küldés'.

Input: client comments + admin edits/AI fixes on a page. Output: a clean working copy the admin shapes freely, frozen into a published version (the client-visible checkpoint) only on an explicit publish — with a full undo trail and a clean 3-state comment model.

Why this mattersToday almost every action (section edit, reorder, CSS, per-section AI, batch apply) freezes a new immutable version, which locks all prior comments out of further editing and re-running. That is the core UX mismatch. Making the working copy mutable and the version-mint explicit is the one change that unlocks everything else.
flowchart LR
  C["Client<br/>comments"] --> W["WORKING copy<br/>(mutable, admin-only)"]
  A["Admin edits<br/>+ AI fixes"] --> W
  W -->|"edit freely,<br/>undo, re-run AI"| W
  W -->|"Ügyfélnek küldés →"| P["PUBLISHED version<br/>(frozen, client sees)"]
  P -.->|"forks fresh"| W

1 · The one big change — mutable working version

TL;DRA version is created ONLY on explicit publish. Everything else edits the working copy in place.

Input: any edit (HTML, CSS, reorder, per-comment AI, regenerate-all). Output: the SAME working version, rewritten in place — no new version row. Publish freezes it → published, and forks a fresh working copy (n+1).

Why it mattersThis relaxes CLAUDE.md 'hard rule #4' (versions immutable) — but only for the working copy; published versions stay frozen forever, and each edit still keeps untouched sections byte-identical. It's deliberate enough to get its own written decision record (ADR-0001). The good news: the recent refactor already built a 'staging' copy that behaves almost exactly like this, so we're generalising, not inventing.
flowchart TD
  E1["section edit"] --> W["working copy"]
  E2["CSS edit"] --> W
  E3["reorder"] --> W
  E4["per-comment AI"] --> W
  E5["regenerate-all"] --> W
  W -->|"NO new version"| W
  W ==>|"PUBLISH only"| V["new published version"]

2 · Comments get one clean state set

TL;DRnincs kész ⇄ kész ⇄ elvetve — toggle freely in BOTH directions while working; 'locked' is derived once published.

Input: a submitted comment (always lands 'nincs kész'). Output: the admin freely moves it not-done ⇄ done ⇄ rejected — ALL three reversible (elvetve is NOT a dead-end: it can go back to nincs kész or kész) — while the version is working; AI-resolved ones get a 🤖 badge; once the version publishes, its done/rejected comments become read-only 'locked'.

Why it mattersToday there are 8 tangled statuses and a client-side 'Kész' toggle that caused accidental marking. Collapsing to 3 fully-interchangeable states (plus a derived locked) makes the whole reviewer legible — and the client just writes + submits, never marks anything done. The AI 'Kérdés van' flag becomes a side-note on a not-done comment, not a separate state.
stateDiagram-v2
  [*] --> nincs_kész: submit
  nincs_kész --> kész: admin / AI 🤖
  kész --> nincs_kész: revert (HTML kept) + re-run
  nincs_kész --> elvetve: reject + note
  elvetve --> nincs_kész: un-reject
  elvetve --> kész: un-reject → done
  kész --> elvetve: reject
  kész --> locked: publish
  elvetve --> locked: publish
  note right of locked: read-only<br/>in a frozen version

3 · Publish & version history — the only checkpoint

TL;DRPublish is the only thing that mints a version; while the admin edits, the client is locked; any past version is browsable read-only from the dropdown.

Input: 'Új verzió / Ügyfélnek küldés'. Output: a warning popup (X carry forward, Y close) → working freezes to published (client unlocks + auto-advances ~20s) → not-done comments re-anchor onto a fresh working copy STILL as not-done (with a faint 'V1' origin badge); done/rejected freeze. There is NO destructive 'restore' — instead the existing version-history dropdown loads any past version's HTML AND its comments in their frozen state, read-only ('↩ Legújabb' returns to newest). And the moment the admin makes a real edit, the client view shows '🔧 Feldolgozás alatt' and can't comment until the next publish.

Why it mattersThe client should keep seeing the last approved page until the admin deliberately ships — no half-finished states leaking out, which is why editing locks the client. Carry-forward keeps unfinished feedback alive across the checkpoint instead of orphaning it (the old immutable model couldn't). We deliberately dropped a destructive 'undo the publish' restore in favour of the read-only history browse that already works — safer, and no data is ever deleted.
flowchart LR
  W["working n<br/>(admin edits → client locked 🔧)"] -->|publish| WARN{"warning popup<br/>carry X · close Y"}
  WARN --> P["published n<br/>(client unlocks)"]
  P --> W2["working n+1<br/>(not-done re-anchored,<br/>still not-done, 'V1' badge)"]
  P -.->|"version-history dropdown<br/>(read-only: HTML + frozen comments)"| H["browse any old version"]

4 · Full per-edit undo (strict LIFO — you chose the strong version)

TL;DREvery edit is reversible as a strict last-in-first-out stack (undo pops the most recent) — and the same log powers the audit download.

Input: each in-place edit. Output: an ordered EditEvent record {what changed, before-state, AI prompt + raw output, when, who}. Undo pops the MOST RECENT edit and restores its before-state (strict LIFO — no out-of-order undo of an arbitrary earlier edit). The very same records export as the JSON + Markdown audit ('the exact prompts given to AI and each output').

Why it mattersMutating in place would normally mean 'no fine-grained undo' — so we keep an append-only history where each edit writes a new immutable snapshot and just moves a pointer. One log does three jobs: undo, per-section state, and the prompt-level audit. This is more than the spec's lighter 'section undo' — it's the safety you asked for so editing freely never feels risky.
flowchart LR
  S0["state 0"] --> S1["state 1<br/>+EditEvent"]
  S1 --> S2["state 2<br/>+EditEvent"]
  S2 --> S3["state 3<br/>+EditEvent"]
  S3 -.->|undo| S2
  S2 -.->|undo| S1
  S3 ==>|export| AUD["audit<br/>JSON + MD"]

5 · Faster by design — your performance question, answered

TL;DRThe single backend path is NOT the slowness. The real limit is a per-page lock, which the new model removes.

Input: your worry about 'communication limited to a single path / multiple editing paths at once'. Output: keep the single DB writer (it's tiny + safe); the actual block is staging_run_in_flight — one AI run per page at a time, because today all edits share ONE blob. New model: each edit = its own snapshot → different sections edit/run in parallel. Swap the page lock for a per-section guard.

Why it mattersBreaking the single-writer would risk SQLite corruption for ~zero gain — the heavy work (Gemini) ALREADY runs concurrently in spawned workers, off the main path. The thing that actually feels slow/blocking is 'you can't touch the page while one AI run is going'. The blob-per-edit design dissolves that naturally. Rule for Instance 2: measure first, then only swap the lock — don't over-build concurrency the profile doesn't justify.
flowchart TD
  subgraph TODAY["today: one shared blob"]
    R1["AI run A"] --> LOCK["page LOCK<br/>(must wait)"]
    R2["AI run B"] --> LOCK
  end
  subgraph NEW["new: snapshot per edit"]
    SA["edit sec 1"] --> OK["run in parallel"]
    SB["edit sec 2"] --> OK
  end
  TODAY -.->|rebuild| NEW

6 · The bug cluster this also kills

TL;DRWrong cross-page counts, duplicating drafts, image+text split, and rejected-shows-AI-text — all fixed by clean rules.

Input: the live-QA bugs. Output: count = ALL open comments project-wide (22 shows 22, not 7); the server becomes the single source of truth for drafts (no more nav A→B→A duplicates); one comment = body + image atomically; rejected shows exactly 'Elvetve — note', never AI text.

Why it mattersThese all share one root: state lived in too many places (localStorage + server + a merge race, counts from the last batch only). The rebuild centralises each rule in one tested module — the counts, the draft reconcile, the comment state — so the bugs can't reappear by drift.
mindmap
  root((clean rules))
    counts
      project-wide<br/>not last batch
    drafts
      server is truth
      no dupes on nav
    comment
      body+image<br/>one row
    rejected
      Elvetve + note<br/>never AI text

7 · How we build it — full rebuild, no backfill

TL;DRClean schema + rewritten client layer, in 6 milestones, tested at every green step.

Input: the locked decisions (full rebuild, no live data, full undo, JSON+MD audit). Output: pure cores first (state + lifecycle + counts + undo) → fresh clean tables (no migration) → server wiring → client/reviewer UI rewrite → live Chrome QA at 1280/768/390. A code-tag backup before the schema swap; branch off production; push only when you say so.

Why it mattersBecause there's no precious live data, we skip the risky backfill entirely and get a clean foundation. Pure logic gets built and tested before any wiring, so the risky parts are proven in isolation. The next instance turns this into an excruciatingly concrete checklist (and writes ADR-0001 + the click-by-click user journeys first).
timeline
  title Rebuild milestones
  M1 : core comment states
  M2 : lifecycle + counts + undo core
  M3 : clean schema (no backfill)
  M4 : server wiring + per-section concurrency
  M5 : client + reviewer UI rewrite
  M6 : live Chrome QA → iterate
Click-by-click journeys (14 scenarios) →Expected-Behavior Oracle (review + sign off) →Technical handoff (machine-facing) →Feature spec →Reviewer-refactor handoff (the seams) →