Newest entry on top.
Mobile full-parity campaign — Phases 1a + 2 (tablet layouts + Manage data layer)
Continued the campaign in green, committed slices:
- Phase 1a — tablet content layouts. Gallery fills the full width on tablet/wide (larger cell target;
was capped at 900px; phone sizing byte-identical). New
components/ContentColumn.js(RN counterpart to the web's 960px reading column) wraps Generate/Single/Manage on tablet; Gallery opts out. Component test guards the "same content at every size" invariant.test:mobile46/46. - Phase 2 — Manage data layer.
lib/storage.jsgains the on-device user overlay: user blocks CRUD (.dpl),.js/.jsonsidecars, nested folders (recursive walk), areadUserTreebuilder mirroring the web tree model, folder create/delete + entry move/rename (10 tests).lib/listOps.jsports the weblistEditorOps.js(sort/dedupe/AI parse+merge) verbatim; Sort + Dedupe wired into the Manage list editor. New parity-gate stepcheckListOpsasserts the port stays behaviorally identical to the web source.test:mobile60/60, parity + metro gates green.
Data-layer feasibility confirmed (see ../../plans/mobile-parity.md):
metroLoader exposes the whole built-in catalog and the metro catalog inlines raw .dpl/list source, so
the built-in tree + source-view + override are all buildable on device. Next: the Manage UI (two-root
tree reusing readUserTree + built-ins, the block/DPL editor reusing the GenerateScreen editor, folder
editor), then Single two-pane on tablet, then wire the overlay into metroLoader + flip the strict gate.
Mobile ⇄ web FULL-parity campaign — Phase 0 (audit + foundation)
Owner set a hard mandate: the mobile (Android/Expo) app must be at COMPLETE feature + appearance parity
with the web app — no exceptions, no size-based feature loss, no "simpler mobile" version — the whole app
on every device (phone AND tablet) to the extent the platform allows, enforced behind the release
quality gate. The only sanctioned non-parity is the SFW/NSFW build variant (the --tier=sfw catalog
physically removes NSFW for the all-ages Play listing; an NSFW APK keeps it) — a compile-time split, not a
feature drop.
Combed every mobile screen/component against its web counterpart and the existing parity framework
(metro-parity-check engine parity, mobile-parity-check ported-catalog + surface markers, jest-expo
component tests, mobile-visual-parity). Current state is green but has real gaps vs. the mandate:
- Manage is the big one — mobile
ManageScreenis lists-only (flat user word-lists), while web Manage is a full content manager (Blocks + Lists roots, folder tree, DPL/block editor with Insert/Refine/Modify-Draft + JS sidecar, list editor Entries/Raw/Sort/Dedupe/AI-Expand, folder editor, override/restore). The parity gate didn't even check Manage, so it hid the gap. - Tablet/responsive — the app is single-column at every size; web has phone/tablet/wide tiers with two-pane master/detail on tablet.
- Visual parity was phone-only (390px).
Wrote the authoritative campaign + gap audit + phased plan: plans/mobile-parity.md
(and strengthened the standing instruction in systems/mobile.md). Phase-0 deliverables (this commit, all
green):
- Multi-size visual parity —
mobile-visual-parity.mjsnow shoots the full phone→tablet matrix (phone-small 360 / phone 390 / phone-large 430 / tablet-portrait 834 / tablet-landscape 1112) intoartifacts/mobile-parity/<size>/, with a--size=<id>filter. Directly serves the "phones and tablets, no size-based feature loss" mandate. - Responsive foundation —
lib/responsive.js(useResponsive()+ pureresolveResponsive), breakpoints mirroring the web tiers (phone ≤768 / tablet 769–1024 / wide >1024) withtwoPane+ capped reading column; unit-tested (lib/__tests__/responsive.test.js). The per-screen tablet layouts build on this. - Test hygiene — fixed the jest FlashList mock so
ListEmptyComponentis keyed (killed the "unique key" warning ManageScreen emitted under test).
Verified: test:mobile 44/44 (was 40), mobile:parity green, metro:parity green, lint 0 errors. Phases
1–4 (per-screen tablet layouts; the full RN Manage port reusing the existing GenerateScreen DPL editor;
override/restore + runtime overlay; flip the strict Manage gate on) follow — each lands green.
Manage tab — fix the phone/mobile editors (right pane)
Comprehensive web-vs-mobile comparison of the Manage tab (both code and live screenshots), then
fixed everything that was actually broken on the phone layout. Verification method note: the
Claude-in-Chrome automation browser here is fixed at ~1707 CSS px and ignores resize_window, so true
narrow viewports were obtained by loading the dev app (localhost:5173) inside a same-origin iframe
sized to 360/390/768/1000 px — media queries evaluate against the iframe's width, so the real mobile CSS
renders and can be screenshotted + measured (matchMedia('(width<=768px)') confirmed).
What was fine on mobile (left untouched): the master tree view, the list-entries virtualized rows, and the folder editor all render cleanly at 390 px.
Three real bugs found + fixed (all in the right-pane editors):
- Block editor — the "Modify / Draft" combo overlapped the code.
DplAskCorneris absolutely pinned to the DPL box's top-right corner. Its intended clearance —padding-top: 2.5remon.cm-content(indpl-refine-toolbar.css) — is silently defeated by CodeMirror's own base padding (measured computedpadding-top= 4px, not 40px; same source-order override theeditorChrome.jstheme documents for the gutter). On desktop the pane is wide enough that short lines never reach the corner, so it only bit on the narrow phone pane, where wrapping front-matter ran straight under the buttons. See fix-patterns. - Editor heads squeezed the name field to ~21px.
.mg-editor-headis anowrapflex row tuned for a wide desktop pane; at ~360px the.mg-name-inputcollapsed to an unreadable/uneditable sliver (measured 21px). - List tools squeezed the entries search to ~24px. Same
nowrapcause on.mg-list-tools.
Fix (all in targets/web/frontend/styles/components/manage-responsive.css, one new
@media (width <= 768px) block, every selector scoped to .workspace.manage so desktop and the
Generate composer are untouched):
- Heads + list-tools
flex-wrap: wrap; the name input and the entries search each take a full-width first line (flex: 1 1 100%), the rest wrap beneath. - On phone the Modify/Draft combo un-pins:
.dpl-editor-wrapbecomes a flex column and.dpl-ask-cornergoesposition: static; order: -1; align-self: flex-end— a right-aligned row above the editor, so it can never overlap the code (no dependence on the overridden CodeMirror gutter). - Its popover would be clipped by
.mg-editor-body { overflow: hidden }, so on phone it becomes a full-width bottom sheet (position: fixed; inset: auto 0 0; z-index: 91) over a dimming scrim — the same treatment the header submenus already use (mobile-sheets.css).
Verified (measured live in the iframe, before → after): corner absolute→static, no vertical
overlap with .cm-content; name field 21px→295px; entries filter 24px→295px; popover now fixed
full-width. Desktop (≥769px) confirmed unchanged (corner absolute, name 14rem). Breakpoints 360/768
mobile, 769+ desktop all correct. Added two Playwright regression tests to
tests/e2e/responsive.spec.js (block-editor corner clears the code + head fields usable; list filter
usable width) — the manage e2e group is 5/5 green. lint (0 errors) + lint:css clean.
CSS-only + a test; mobile-only media rules, so no visual-baseline churn on desktop. No data-practice change → legal docs untouched. PATCH-level.
With the two hard gates (CodeQL + SonarCloud) green, the only remaining BLOCKED reason on PR #50 was
main's require-conversation-resolution rule against ~15 unresolved CodeRabbit threads. Owner chose
"address all, then release", so worked through every one on merit (no blanket resolves):
- Own hardening code: guarded
decodeURIComponentin the visual-parity server (a/%request no longer crashes it); derivedbuild-metro-catalogimport specifiers fromdirname(outPath); tightenedmetroLoader.testassertions to rejectundefined. - Mobile stability: shared
fetchWithTimeout(AbortController + 120s, merges caller signal) across everyimageProviders.jsfetch; 60s timeout race onstorage.jsremotedownloadAsync;keys.jssetKeyreturns success + BYOK field warns on failed keystore write. - Mobile error handling / correctness: Gallery load-failure retry +
deleteSelectedguard; SingleScreendoConvertbusy-guard; GenerateScreen insert targets the active Prompt/Negative field + monotonic batch-id (fixes fixed-seed id collision) + always-clear rewrite status; ComfyUIbatch_size≥1; ManageScreen list names disallow/;index.jsexplicit./App.js.
Verified: Node coverage gate (352 tests, thresholds held, metroLoader ~87%), metro:parity (150 gens
identical), mobile jest (40/40), lint 0 errors, prettier clean. Committed as 2 focused commits (+ the
earlier gate-fix commits), pushed; then replied to + resolved all threads. Paused for merge go-ahead.
Release-harden 2.52.0 — clear the PR #50 gate blockers on merit
Picked up the stalled 2.52.0 release. PR #50 (release/2.52.0 → main) was BLOCKED by two real
checks (every other check green): CodeQL code-scanning and the SonarCloud quality gate. Pulled
the live details and fixed each on merit rather than silencing anything. Work done on the Windows repo
(PowerShell + real push access), verified with the real toolchain before committing.
What was actually blocking (ground truth from the PR):
- CodeQL — 3 high alerts: "uncontrolled data used in a path expression" (path traversal) in
scripts/mobile-visual-parity.mjs. The dev file-server fedreq.urlstraight intojoin(OUT, …)→statSync/readFileSync. Fixed with an absolute-root containment guard (resolve underOUT_ROOT, reject escapes →index.html). Verified functionally: raw + URL-encoded (..%2f,%2e%2e) traversal all fall back toindex.html; normal asset paths resolve unchanged. - SonarCloud — coverage 0% on new lines: the earlier
metroLoader.jscoverage exclusion (added to go green) was starving Sonar's lcov too. Reverted it and wrote a real test (tests/mobile/metroLoader.test.js) that earns ~87% and proves metro↔node engine parity. Only the generatedmetroCatalogData.jsstays excluded (data) — now also from Sonar analysis/coverage/CPD so the CI-built blob isn't scanned. - SonarCloud — Reliability D + Maintainability + CodeQL 8 dead-code notes: aligned the numeric
Steps/CFG/Seed assertions in
single.test.jswith Sonar's string-valued map inference, switched totoBeNull/toHaveLength, and removed 8 unused imports/vars across the mobile target.
Landmine found + fixed (this machine, not the cloud clone): the new metroLoader.test.js passed
standalone but flaked in the full parallel suite (88 vs 89 block names). Root cause: it compared a
build-time metro snapshot against a live nodeLoader read while, in parallel, manageFs.test.js
writes transient zz-manage-test/* fixtures into the shared engine/data/blocks root and 30+ workers
stress the same files (a Windows sharing violation can momentarily hide a real block from a scan). Fixed
by moving the catalog build + a clean-state snapshot of nodeLoader's names and parity generations into a
Vitest globalSetup (tests/setup/metro-catalog.globalSetup.js) that runs before any worker spawns;
the test now compares frozen snapshots via inject. Deterministic green across repeated full runs.
Verification (all on merit, local): eslint 0 errors, prettier clean, check:docs (375 links),
smoke, test:coverage (352 tests, coverage above every threshold, metroLoader.js measured ~87%, 3×
deterministic), mobile jest (test:mobile, 40 tests / 10 suites), mobile:parity PASS, metro:parity
PASS (150 seeded generations identical). Committed as 3 focused commits on release/2.52.0 with their
changelog entries; pushed to re-trigger CI. No version bump — 2.52.0 hasn't shipped (origin/main still
a9eeaf7, no v2.52.0 tag); these harden the pending release. Merge to main awaits owner go-ahead.