Skip to content

[codex] isolate web UI demo under examples#225

Merged
thewrz merged 39 commits into
mainfrom
mockup
Jun 19, 2026
Merged

[codex] isolate web UI demo under examples#225
thewrz merged 39 commits into
mainfrom
mockup

Conversation

@thewrz

@thewrz thewrz commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Moves the web UI mockup into examples/web_ui_demo/ so the PR no longer carries API/backend changes from the long-lived mockup branch.
  • Adds an example-local static/proxy server (examples/web_ui_demo/server.mjs) so the demo can call a local SpecR API without changing src/ to serve demo assets or CORS.
  • Keeps demo launch wrappers inside the example folder and documents the example boundary in examples/web_ui_demo/README.md.

Boundary Check

The final branch diff against main is limited to examples/web_ui_demo/**. The explicit non-example audit returned no paths:

git diff --name-only main..HEAD -- ':!examples/web_ui_demo/**'

Validation

  • bash -n examples/web_ui_demo/Start-SpecR.sh
  • node --check examples/web_ui_demo/server.mjs
  • node --check examples/web_ui_demo/js/*.js (run per file)
  • pnpm build

Notes: PowerShell is not available in the local environment, so examples/web_ui_demo/scripts/windows/Start-SpecR.ps1 was not parse-checked locally. A localhost smoke test confirmed static files serve through server.mjs; /health proxied to an unrelated process already running on local port 3000 in this environment, so live SpecR API compatibility was not asserted.

Summary by CodeRabbit

  • New Features
    • Added a complete SpecR Web UI demo with interactive spec map visualization, TOC builder, library management, citation popovers, and coordination report rendering.
    • Added a standalone demo server that serves the UI, proxies API requests, and safely serves static assets.
  • Documentation
    • Added a README with setup, environment variables, and one-command local launch instructions.
  • Chores
    • Added .gitattributes to standardize line endings across Windows batch/PowerShell and Unix shell scripts.

thewrz and others added 30 commits June 12, 2026 21:23
…ing, loopback-default HOST

- listSpecs query (LEFT JOIN paragraph counts) exposed as GET /specs
- getSpecTree (populated tree + cross-references) exposed as GET /specs/:id/tree
  (previously REST returned parts:[] always; the populated tree was MCP-only)
- express.static(public/) mounted ahead of the router for the demo SPA
- HOST env (default 127.0.0.1) so the unauthenticated demo server is never
  LAN-exposed by accident; opt-in via HOST=0.0.0.0
Drafting-room blueprint UI served from public/ (vanilla ES modules, no build):
- drag-and-drop multi-file ingest via POST /parse with job polling, per-file
  stage progress, and 429 rate-limit backoff
- CSI hierarchy sheets: faithful getLabel port (PART N - / N.N / A. / 1. / a.),
  collapsible parts/articles, [NOTE] callouts, vanish tags
- cross-reference web: SVG arc diagram of section-to-section citations with
  loaded / in-library / unresolved states; client-side resolution by section
  number so links heal regardless of upload order
- in-text Section XX XX XX linkification (notes included) with jump-and-flash
  navigation; per-sheet citation chips and collapsible standards list
Start-SpecR.bat -> scripts/windows/Start-SpecR.ps1 (PowerShell 5.1, ASCII-only):
- portable Node.js 22 + pnpm + PostgreSQL 16 into .specr-runtime/ — no admin
  rights, no system installs, removable by deleting one folder
- initdb (trust auth, loopback-only) + pg_ctl on port 5439, reused across runs
- pnpm install --frozen-lockfile, migrate, seed, build, then node dist/index.js
- pre-checks the app port, opens the browser only once the server answers
- overrides: SPECR_PORT / SPECR_PG_PORT / SPECR_DATABASE_URL / *_VERSION
- .gitattributes pins CRLF for .bat/.ps1 and exempts UFGS latin1 fixtures
Double-clicking on a locked-down machine hit PSSecurityException /
UnauthorizedAccess ('running scripts is disabled on this system'):

- preflight (best-effort): Unblock-File clears Mark-of-the-Web from zip
  extracts, and Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
  registers the standing exception so direct .ps1 runs work too
- launch via Invoke-Expression of the script TEXT: command text is not a
  script file, so execution policy -- including GPO-enforced
  Restricted/AllSigned that ignores -ExecutionPolicy Bypass -- cannot
  block it
- repo path travels via SPECR_REPO_ROOT env (never interpolated into PS
  string literals), so spaces, parens, and apostrophes in the path are
  inert; PS1 falls back to it when $PSScriptRoot is empty under IEX
- explicit exit-code propagation (required in -Command mode) so the
  bat's errorlevel check still surfaces failures
Every installer step is now visible in the cmd window:

- Invoke-Native takes a step label (>> prefix) and streams the tool's own
  stdout/stderr line by line (initdb, pg_ctl, createdb, npm) instead of
  discarding it
- Get-Download rewritten as a manual stream copy with a live percent /
  MB counter, so the ~320 MB PostgreSQL archive never looks frozen
- extraction, reuse-of-cached-artifact, env settings, and resolved tool
  paths (node/pnpm locations, npm global prefix) are all echoed

pnpm install failure (reported: 'could not install pnpm') hardened:

- npm's full output now streams (dropped --loglevel=error) so the real
  cause is visible
- $global:LASTEXITCODE = 9009 sentinel in Invoke-Native -- a missing
  executable previously left a stale exit code that could read as
  success or mask the real failure
- after install, pnpm.cmd is resolved via 'npm prefix --global' and that
  dir is prepended to PATH (fresh installs often don't have %APPDATA%\npm
  on the session PATH yet)
- throw messages now include the npm exit code, likely causes (proxy /
  registry block / antivirus), and a manual workaround
…adlock)

A real run stalled right after pg_ctl printed 'server started' -- the
demo's HTTP server never came up. Root cause: pg_ctl start spawns the
postgres daemon, which inherits duplicates of the console pipe handles
that Invoke-Native's capture pipeline (2>&1 | ForEach-Object) reads.
pg_ctl exits, but the pipe never reaches EOF while postgres holds the
write handle, so the script blocked forever before the psql check,
dependency install, build, and node startup.

- new Invoke-NativeUnpiped: Start-Process -NoNewWindow -PassThru +
  Process.WaitForExit() -- no pipes, so no EOF dependency; waits only on
  pg_ctl itself, never the postgres descendant (Start-Process -Wait on
  PS 5.1 waits for the whole tree and would re-hang)
- pg_ctl start routed through it, args as a single pre-quoted string
  (space-safe paths, -o "-p PORT" reaches postgres as one token)
- clarifying console line: 'server started' is PostgreSQL; the SpecR
  web server follows the build steps
…errupted runs

Field report: pg_ctl start 'failed' per the exit-code check while
postgres.log ended with 'database system is ready to accept
connections' -- the server was fine, the exit code lied (PS 5.1
Start-Process -PassThru can hand back a Process whose ExitCode reads
null, and null -ne 0 is true).

- Invoke-NativeUnpiped now uses raw [System.Diagnostics.Process]::Start
  (UseShellExecute=false): still no pipes (no daemon EOF deadlock),
  WaitForExit on the launcher only, and a reliable non-null ExitCode
- readiness is now decided by the server itself: poll pg_isready for up
  to 30s after pg_ctl start; proceed (with a note) when the server
  accepts connections even if pg_ctl's exit code claimed failure; only
  fail when the probe never succeeds
- on real failure, print the last 40 lines of postgres.log inline plus
  targeted hints (missing VC++ redistributable when no log exists, port
  conflicts, antivirus, elevation) instead of 'see the log'
- self-heal interrupted runs: stop orphaned postgres.exe processes from
  OUR runtime dir (they hold the port + shmem), bump to the next free
  port past foreign listeners, and when the server is already running
  read its actual port from postmaster.pid line 4
- proactive warnings: vcruntime140.dll missing, elevated console
…ERT_LOCALLY)

Behind a TLS-inspecting proxy or antivirus, the corporate root CA lives
in the Windows certificate store -- which PowerShell uses (our direct
downloads worked) but Node ignores in favor of its bundled CA list. So
npm/pnpm fail every registry request with
UNABLE_TO_GET_ISSUER_CERT_LOCALLY and crawl through retry backoff,
which is also why pnpm install was so slow.

Bootstrap now exports LocalMachine/CurrentUser Root+CA stores to
.specr-runtime/windows-ca-bundle.pem and sets NODE_EXTRA_CA_CERTS for
the session before any npm/pnpm/node call -- TLS verification stays ON,
Node just gains the same trust roots the OS already has. Opt out with
SPECR_NO_SYSTEM_CA=1.
…d notes properly

tree.parts holds ROOT nodes, not necessarily part-type nodes — ARCAT
preamble (title lines, copyright, specifier note) lands at root by
design. The sheet renderer labelled every root by raw array index, so
preamble scraps displayed as 'PART 1..10' and the real parts became
PART 11/12/13 with articles numbered 11.1.

- PART numbering now counts only part-type nodes (articles correctly
  read 1.1 again)
- root-level notes render as amber note callouts
- remaining root continuations render as dim italic front-matter lines
- empty-text roots from pre-fix DB rows are skipped defensively
Footer section chips previously only jumped to the referenced sheet.
Each chip is now a split control:

- main button walks the citation sites of that section WITHIN this
  spec's body — cycling on repeated clicks with a position counter
  (2/3), auto-expanding collapsed parts/articles on the way, and
  flashing a locate ring on the found link
- the new arrow tail keeps the jump-to-referenced-sheet behavior
  (loaded -> jump; in-library -> load hint; unresolved -> walk only)

In-body links still hop to the target sheet, so citations can be
ping-ponged: walk them here, jump across, walk the next spec's.
Hovering any in-body section reference shows a floating < n/N > pill:

- next/prev step through ALL citation sites in that spec's body in
  document order, wrapping at the ends
- each step locate-flashes the link, auto-expands collapsed
  parts/articles on the way, and smooth-scrolls it to center
- the pill is position:fixed and deliberately stays put under the
  cursor while the page scrolls beneath it, so next-next-next walks
  the whole spec without chasing the control
- grace-period hide on mouse-out; dropped when a citation link is
  clicked (cross-sheet navigation takes over)

locateLink/expandAncestors extracted from the chip walker and shared.
Proprietary files can't be shared for diagnosis, so failures must be
self-explanatory locally:

- failed queue items show [HTTP status / job id] before the server's
  full error message, plus a hint that the complete response is in the
  browser console
- uploadSpec/waitForParseJob log the full server response / job object
  via console.error and attach status/jobId to thrown errors
Field report: walking references scrolled correctly but never visibly
highlighted the in-body citation. Root cause: the 1.5s locate pulse
started at CLICK time while smooth scrollIntoView was still in flight —
on these sheets the target is often thousands of pixels away, so the
animation completed before the link ever entered the viewport.

- persistent is-current marker (orange outline) applied immediately and
  moved on each step, so the found citation stays identifiable
- the pulse now fires via IntersectionObserver when the link actually
  arrives in view, regardless of scroll distance; observer disconnects
  after firing (10s hygiene timeout otherwise)
- pulse made bolder (double ring) now that it fires where you're looking
Field report: a specific spec listed a reference in the footer but
clicking it could not snap to the citation in the body. Root cause:
the ingest extractor matches 'Section \d\d\s+\d\d\s+\d\d' — tolerant of
non-breaking spaces and doubled spaces, both routine in Word text — and
stores a normalized targetSection, while the SPA linkifier demanded
single ASCII spaces. The ref row existed; the body link was never
created; the chip had nothing to walk to.

- SECTION_PATTERN now tolerates \s{1,3} between digit groups (covers
  NBSP); matched text is displayed verbatim but normalized into
  link.dataset.section for status lookup, navigation, and walking
- walkToCitation matches on dataset.section instead of textContent
- when a walk still finds nothing, the chip no longer fails silently:
  a toast says whether the section number appears in the sheet text at
  all, and the console gets specifics for reporting

Reproduced with a synthetic DOCX carrying both NBSP and double-space
citations: pre-fix zero body links; post-fix both linkify, walk 1/1,
and snap with the is-current marker.
…lings (#122)

Port of the generator/markdown.ts fix to the mockup renderer: number a
node's children by an ordinal that only advances past numbered siblings,
so specifier-note banners no longer shift the list (1..15 rendered 5..20).
New consumesNumber/appendNumberedChildren helpers; getLabel unchanged.

Refs #122
Two demonstrable edit features on the linkage-console SPA, showcasing the
backend's deterministic cross-reference integrity (not for shipping).

Feature A — delete a paragraph: a hover ✕ on each paragraph removes it; the
spec_references ON DELETE CASCADE drops the contained citation in one statement.
The confirm dialog counts references across the whole deleted subtree.

Feature B — inline editor: click a paragraph to edit its text; on save, the
removed citations are detected by per-section occurrence counts, and a modal
offers Cancel / delete-the-reference / delete-reference-and-remove-spec-from-
project. The board is backed by one hidden demo project so removeSpecFromProject's
broken-ref cascade spans the corpus; the masthead shows a server-computed
BROKEN REFS count, and re-dropping a removed section heals the broken links.

Backend (src/, CI-checked):
- getSpecTree references now expose id + sourceParagraphId
- new DB mutations: deleteParagraph, updateParagraphText, deleteReference, deleteSpec
- new routes: DELETE /specs/:id, DELETE+PATCH /specs/:id/paragraphs/:pid,
  DELETE /specs/:id/references/:rid  (UpdateParagraphBodySchema, shared pg-error helper)
- removeSpecFromProject cascade also breaks UNRESOLVED section-only citations,
  gated on target_spec_id IS NULL so a citation resolved to a surviving
  same-section spec (same section, other source) is never collateral-damaged

Frontend (public/, not CI-linted):
- api.js mutation + project client; modal.js (promise-based confirm/choice);
  tree.js per-paragraph ✎/✕ + textarea editor + broken-ref styling;
  app.js demo-project bootstrap, mutation orchestration, broken-ref counter
- self-citations and multi-source duplicate sections handled so an edit can
  never delete the spec being edited and removes every loaded same-section sheet

Tests: deleteParagraph cascade, updateParagraphText, deleteReference, deleteSpec
(409 on project pin), and the multi-source cascade regression. Full suite green
(601 unit, 258 integration); verified end-to-end in the browser.
* fix(parser): tolerate dirty SEC metadata

Sanitize XML-unsafe legacy control characters before SEC XML parsing while still rejecting null bytes. Allow SEC files without SCN metadata to parse as unknown so the existing inference stage can recover from document content.

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

* fix(parser): decode SEC section entities before prefix strip

Decode SCN entity references before removing the SECTION prefix so entity whitespace still normalizes to canonical section numbers.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Support SCN and STL metadata nested inside SEC heading wrappers such as HL3, covering UFGS 11_72_13 without weakening the missing-title validation.

Co-authored-by: Claude <noreply@anthropic.com>
thewrz and others added 6 commits June 13, 2026 14:29
Bring 34 commits of main's backend features into the mockup demo branch so
the new APIs (document concurrency + advisory locks, editability/conventions,
revision nomenclature, manual assembly + front matter, SEC output renderer,
source facts, spec diff/merge, deep paragraph nesting, header/footer config,
Scalar API docs) can be exercised through the linkage-console UI.

Conflict resolutions (14 files):
- parser/sec: take main's pr5/pr6/pr7 deep-nesting depth map (#215) over
  mockup's pr5 cap; mockup's SEC metadata fixes (#173/#175) already on main.
- api/paragraphs + db/queries/paragraphs: keep mockup's deleteParagraph; adopt
  main's gated, optimistic-concurrency updateParagraphText (UpdateParagraphResult).
- api/router: union of mockup demo routes (list/tree/delete spec, delete/edit
  paragraph, references, coordination, required-sections, libraries) and main's
  new routes (locks, style-source, diff, merge, manual/revision generate); parse
  stays unthrottled for stakeholder demos.
- ast schemas/index: drop mockup's duplicate CreateRevision/UpdateParagraph
  (main owns them via revision-schemas.js + the expectedVersion superset); keep
  SetRequiredSections; restore the missing SectionNumberFormatSchema import.
- mcp/tools: register both coordination_report (mockup) and get_spec_diff (main).
- openapi: split the interleaved PATCH /projects/{id} and POST
  /projects/{id}/generate into two complete operations.
- .gitignore: narrow main's public/ ignore to public/scalar/ so the committed
  mockup SPA stays tracked.

Migration renumbering: revert division_general_specs to its canonical 022
(byte-identical to main), move mockup's new migrations after main's highest —
022_create_required_sections → 031, 024_project_settings → 032. Clean 001–032,
no duplicate prefixes.

Line-cap (max-lines 400) restoration after the merge concatenated both branches'
additions: extract handleCoordinationReport → src/mcp/coordination-handler.ts and
getBrokenRefs/BrokenRef → src/db/queries/broken-refs.ts.

Verified: pnpm build, pnpm lint (eslint + tsc --noEmit + prettier), and
pnpm test (1004 unit tests) all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The main merge brought constraints and handler behavior that mockup's
pre-existing integration fixtures predated. None of these reflect a product
defect — the real parse/persist flow already satisfies every constraint; only
the raw-INSERT test fixtures bypassed them.

- specs_owner_xor (migration 016): demo-mutation fixtures inserted bare specs
  with no owner. Provide library_id (matching the pattern main applied to its
  own fixtures): UFGS Reference, or distinct libraries for the multi-source
  cascade test. Fixes mutations/edit-mutations/cascade-multisource suites.
- PATCH /specs/:id/paragraphs adopts main's gated handler: z.uuid()-strict
  params (synthetic all-zero-version ids → valid v4) and 400 (not 422) on empty
  text; assert main's contract.
- listProjects now carries sectionNumberFormat (ProjectListItem): assert with
  objectContaining in the refs + MCP list_projects tests.
- GET /specs/:id/tree: testSpecId is seeded with a baseline part in the file's
  top beforeAll, so find this block's 'GENERAL' part by text, not by index.

Integration: 620/623 pass. Remaining: the openapi route↔spec contract gate
(mockup's demo routes are undocumented — pending a doc-vs-exclude decision) and
cpi.integration (parser byte-identical to main; local copyrighted fixture lacks
the ART/PRT styles detectSource keys on — CI skips it as fixtures are gitignored).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@thewrz, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 8 minutes and 58 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e719a00-63b9-423a-82c0-984ebb5fae6b

📥 Commits

Reviewing files that changed from the base of the PR and between 4bc6176 and 6ed4d88.

📒 Files selected for processing (2)
  • examples/web_ui_demo/Start-SpecR.sh
  • examples/web_ui_demo/scripts/windows/Start-SpecR.ps1
📝 Walkthrough

Walkthrough

Adds a complete examples/web_ui_demo directory: cross-platform launch scripts (.bat, .sh, .ps1) and a Node.js static-file/proxy server (server.mjs), a REST API client module (js/api.js), a full SPA with HTML structure (index.html), 2371-line stylesheet (css/app.css), and eight JS modules covering spec-tree rendering, SVG reference web, coordination report, drag-and-drop upload, modal dialogs, labels, and a main application orchestrator.

Changes

SpecR Web UI Demo

Layer / File(s) Summary
Launcher scripts and proxy server
examples/web_ui_demo/.gitattributes, examples/web_ui_demo/README.md, examples/web_ui_demo/Start-SpecR.bat, examples/web_ui_demo/Start-SpecR.sh, examples/web_ui_demo/scripts/windows/Start-SpecR.ps1, examples/web_ui_demo/server.mjs
Adds .gitattributes for CRLF/LF normalization; cross-platform launcher scripts that install deps, migrate/seed/build the API, spawn it in the background, then start the demo server; and server.mjs that serves static assets from the filesystem, proxies API path prefixes to the upstream SpecR API with request body buffering and hop-by-hop header rewriting, and returns JSON 502 on proxy failures.
REST API client
examples/web_ui_demo/js/api.js
Adds getJson/sendJson HTTP helpers with error metadata and 25 exported wrappers covering health checks, spec/library queries, spec-tree retrieval, library mutations, project lifecycle, coordination reporting, paragraph/reference edits, spec removal, and parse-job polling with optional progress callbacks.
Static HTML shell and full CSS
examples/web_ui_demo/index.html, examples/web_ui_demo/css/app.css
Adds index.html defining five view panels (TOC builder, spec map, project settings, library, coordination report) plus upload dock, toast notification rack, and hidden ingestion infrastructure; adds 2371-line app.css with the blueprint-themed design (ink/vellum colors, fonts, status colors), masthead/navigation/workspace layout, TOC and library panels, reference web and coordination report styling, vellum spec-sheet cards with tree rendering and reference footer, responsive tweaks for mobile, broken-ref indicators, paragraph edit overlays, and modal dialogs.
Shared UI primitives
examples/web_ui_demo/js/labels.js, examples/web_ui_demo/js/modal.js, examples/web_ui_demo/js/popover.js
Adds getLabel for CSI hierarchy label generation (parts, articles, PRs); promise-based openChoice/openPicker/openConfirm modals with global overlay, focus trapping, and Escape/backdrop dismissal; and initRefPopover citation-navigation pill that shows/hides with grace-period timing, walks ref-link elements with wrapping, and integrates with locateLink for scroll navigation.
Spec-sheet DOM renderer
examples/web_ui_demo/js/tree.js
Adds linkifyText that converts section references into clickable buttons with loaded/library/unresolved/broken styling, CSI ordinal-number parsing, paragraph edit/delete UI with inline autosizing textarea and async save flow that detects removed citations, collapsible PART/ARTICLE tree rendering with correct ordinal sequencing, citation-walk per section with IntersectionObserver-based pulse animation, outbound-reference footer chips with occurrence cycling and fallback navigation, and renderSpecSheet assembling the complete spec card with header stats and optional TOC add/remove controls.
SVG cross-reference web
examples/web_ui_demo/js/web.js
Adds buildWebModel to collect loaded section nodes, derive ghost nodes (library/unresolved), aggregate directed citation edges with counts, and compute each edge's final status using severity ranking with broken-status override; and renderWeb to draw the SVG arc diagram with horizontal node positions, arc lift/ceiling geometry, path rendering with tooltips, click-to-navigate for loaded edges, mouseenter/mouseleave hover callbacks, and ghost-section IN LIBRARY/UNRESOLVED tags.
Coordination report renderer
examples/web_ui_demo/js/coordination.js
Adds renderCoordinationReport that groups findings by type (present-not-required, required-not-present, dangling-refs), renders each group as a collapsible section with finding rows, click handlers for navigating to sections, per-finding target/reference display, an overall summary chip row with counts, and fallback unavailable message.
Drag-and-drop upload
examples/web_ui_demo/js/dropzone.js
Adds initDropzone with a sequential upload queue, per-file progress list items tracking stage text and fill percentage, uploadSpec + waitForParseJob polling workflow, onSpecReady callback on completion, recursive FileSystemEntry directory traversal for dropped folders, file-extension filtering with skip reporting, drag-depth counter for drop-veil overlay show/hide, and optional add-sections FAB click or context-driven file picker.
Main app orchestrator
examples/web_ui_demo/js/app.js
Adds the 1786-line single-page entry point: global state management (specs, projects, view, TOC, library), toast notifications, localStorage persistence for library-only visibility and active project, full project lifecycle (create/rename/switch/load/sync with source library scoping), TOC builder (sort/group by division, search/exact matching, add/remove entries, server sync with dirty tracking), library manager (select company/client, render division-grouped tree, add/rename/remove specs, drag-drop context and duplicate detection), paragraph edit/delete mutation flows with optional cascading reference removal, board re-render pipeline with link-status resolution and deterministic navigation, and bootstrap initialization (API health check, demo library provisioning, dropzone/popover wiring).

Sequence Diagram(s)

sequenceDiagram
  rect rgba(30, 80, 140, 0.5)
    Note over User,CoordJs: Demo Bootstrap
    participant User
    participant StartScript as Start-SpecR.sh/.ps1
    participant ApiProcess as SpecR API (port 3000)
    participant ServerMjs as server.mjs (port 3001)
    User->>StartScript: run launcher
    StartScript->>ApiProcess: spawn (pnpm install → migrate → seed → build → node dist/index.js)
    StartScript->>ServerMjs: node server.mjs (SPECR_API_BASE=http://127.0.0.1:3000)
  end

  rect rgba(80, 40, 120, 0.5)
    Note over Browser,CoordJs: SPA Init and File Ingest
    participant Browser
    participant AppJs as app.js
    participant ApiJs as api.js
    participant Dropzone as dropzone.js
    participant TreeJs as tree.js
    participant WebJs as web.js
    participant CoordJs as coordination.js

    Browser->>ServerMjs: GET /index.html
    ServerMjs-->>Browser: static HTML/CSS/JS
    Browser->>AppJs: DOMContentLoaded bootstrap
    AppJs->>ApiJs: checkHealth(), listLibraries(), listProjects()
    AppJs->>ApiJs: getRequiredSections(projectId)
    AppJs->>Dropzone: initDropzone({ onSpecReady })
    User->>Dropzone: drop .sec/.docx files
    Dropzone->>ServerMjs: POST /parse (FormData)
    ServerMjs->>ApiProcess: proxy multipart upload
    ApiProcess-->>ServerMjs: { jobId }
    ServerMjs-->>Dropzone: { jobId }
    Dropzone->>ApiJs: waitForParseJob(jobId, onProgress)
    ApiJs-->>Dropzone: job.result (complete)
    Dropzone->>AppJs: onSpecReady(spec)
    AppJs->>ApiJs: getSpecTree(specId)
    AppJs->>TreeJs: renderSpecSheet(spec, sheetCtx)
    AppJs->>WebJs: buildWebModel(specs) → renderWeb(canvas, model)
    AppJs->>ApiJs: getBrokenRefs(projectId)
    AppJs->>ApiJs: getCoordinationReport(projectId)
    AppJs->>CoordJs: renderCoordinationReport(container, report, ctx)
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[codex] isolate web UI demo under examples' accurately describes the main change: moving the web UI demo into a dedicated examples directory with local infrastructure (static/proxy server).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mockup

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 17

🧹 Nitpick comments (2)
examples/web_ui_demo/Start-SpecR.sh (1)

30-38: ⚡ Quick win

Wait for API readiness before launching the demo server.

Line 30 starts the API and Line 38 immediately starts the demo; this can race and surface avoidable “API unavailable” failures during boot.

Suggested patch
 node dist/index.js &
 API_PID="$!"
+
+ready=0
+for _ in {1..60}; do
+  if curl -fsS "http://127.0.0.1:$API_PORT/health" >/dev/null; then
+    ready=1
+    break
+  fi
+  sleep 0.5
+done
+if [[ "$ready" -ne 1 ]]; then
+  printf 'SpecR API did not become ready on port %s\n' "$API_PORT" >&2
+  exit 1
+fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/web_ui_demo/Start-SpecR.sh` around lines 30 - 38, The Start-SpecR.sh
script starts the API process with `node dist/index.js &` and assigns the PID to
API_PID, but immediately launches the demo server with `node server.mjs` without
waiting for the API to be ready, causing potential race conditions. After the
API_PID assignment and before the cd command that precedes launching the demo
server, add a polling loop that checks if the API is ready to accept requests
(you can use curl or similar to probe the API port specified by API_PORT) with a
reasonable timeout and retry count to ensure the API is listening before
proceeding to start the demo server.
examples/web_ui_demo/scripts/windows/Start-SpecR.ps1 (1)

43-56: ⚡ Quick win

Add an API health wait loop before opening the demo.

After Line 43, the script launches the demo flow immediately; if the API is still booting, early calls can fail.

Suggested patch
 $api = Start-Process -FilePath 'node' -ArgumentList 'dist/index.js' -WorkingDirectory $RepoRoot -NoNewWindow -PassThru
+
+$ready = $false
+for ($i = 0; $i -lt 60; $i++) {
+    try {
+        $resp = Invoke-WebRequest -Uri "http://127.0.0.1:$ApiPort/health" -UseBasicParsing -TimeoutSec 2
+        if ($resp.StatusCode -eq 200) { $ready = $true; break }
+    } catch {}
+    Start-Sleep -Milliseconds 500
+}
+if (-not $ready) {
+    throw "SpecR API did not become ready on port $ApiPort"
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/web_ui_demo/scripts/windows/Start-SpecR.ps1` around lines 43 - 56,
The script starts the API process with Start-Process but immediately proceeds to
display connection information and launch the web UI without verifying the API
is ready to accept requests. Add a health check wait loop after the
Start-Process line that launches the API node process. This loop should poll the
API endpoint at http://127.0.0.1:$ApiPort with a reasonable timeout and retry
interval to confirm the API is responding before allowing the script to continue
with the Write-Host statements and the Start-Process call that opens the web UI
demo.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/web_ui_demo/css/app.css`:
- Line 26: The stylelint value-keyword-case rule is failing on the font-family
declarations at lines 26 and 1546. CSS generic font family keywords (such as
serif, sans-serif, and monospace) must be lowercase according to stylelint's
default configuration. Review both the --serif custom property declaration and
the similar declaration at line 1546, and ensure all keyword values within the
font-family lists are in lowercase, particularly the generic font family
keywords at the end of each declaration.

In `@examples/web_ui_demo/js/api.js`:
- Around line 79-82: The console.error() call in the SpecR upload rejection
error handler violates the no-console lint rule for non-test/non-script
JavaScript files. Remove the console.error() statement and instead attach the
diagnostic information to the error object being thrown (the err variable),
allowing the UI layer to decide where and how to display the error. Apply this
same refactoring to the second console.error() occurrence mentioned at lines
171-173 in the same file.

In `@examples/web_ui_demo/js/app.js`:
- Line 440: The console.warn call at line 440 in the app.js file (and similar
console calls at lines 473, 1453, 1465, and 1552) violate the no-console linting
policy enforced for production JavaScript modules. Remove all console.* method
calls (console.warn, console.log, console.error) from these locations in the
app.js file since logging to the console is not allowed in production code
according to the project's linting configuration.
- Around line 1-1778: The app.js file at 1778 lines exceeds the 400-line maximum
specified in the coding guidelines and should be split into separate modules
organized by functional concern. Create the following new module files: a
project manager module containing functions like activeProjectName,
activeSectionNumberFormat, refreshProjectList, switchProject,
saveProjectSettings, loadActiveProjectWorkspace, and initProjectManager; a TOC
builder module containing makeNode, loadedTocEntries, compareTocSections,
groupedTocSections, renderTocBuilder, renderTocDivisionGroup, addTocCandidate,
matchingTocSpecs, renderTocCandidates, refreshTocBuilder, saveTocBuilder, and
initTocBuilder; a library manager module containing selectedLibrary,
companyMaster, clientLibraries, renderLibraryView, selectLibrary,
renderLibraryDetail, renderLibraryTree, addClientLibrary, renameSelectedClient,
removeSpecFromLibrary, and initLibraryManager; a mutations module containing
onDeleteParagraph, onSaveParagraphEdit, commitTextEdit, removeTargetSpecs,
onRemoveSpecFromProject, and onAddSpecToToc; and a utilities module for helper
functions like toast, preview, and subtreeNodeIds. Update app.js to import these
modules and call their initialization functions, keeping only the core state
management, boot sequence, and sheetCtx definition in the main file.
- Around line 1426-1439: The commitTextEdit function calls updateParagraph
before the deleteReference operations in a loop, which means if any delete
fails, the paragraph update has already been persisted to the server while the
catch block returns "cancelled" suggesting the operation failed completely. To
mitigate this UI/state consistency issue, ensure that after catching an error in
the catch block, the app reloads all specs from the server using reloadAllSpecs
and refreshes the board state with renderBoard to reflect the actual persisted
state on the server, so the UI accurately reflects what operations succeeded and
which failed rather than leaving inconsistent state.

In `@examples/web_ui_demo/js/dropzone.js`:
- Around line 77-233: The initDropzone function exceeds the
max-lines-per-function limit of 50 lines and needs to be refactored into smaller
functions. Extract the logical blocks into separate internal functions: create
one function for initializing all DOM event listeners (for dragenter, dragleave,
dragover, drop events), another for setting up button click handlers (pickBtn
and addFab), another for the file input change handler, and consider moving the
nested functions like makeItem, setStage, fail, processOne, pump, accept, and
chooseFiles to be module-level or better organized to reduce the main
initDropzone body to under 50 lines. Ensure initDropzone still maintains its
public interface and calls these extracted functions to preserve the existing
behavior.

In `@examples/web_ui_demo/js/modal.js`:
- Around line 55-61: The current focus trapping in the onKey event handler only
restricts Tab navigation to action buttons via trapFocus(event, buttons), which
leaves body inputs unreachable and allows focus to escape the modal in
openPicker. Expand the focus trapping implementation to collect all tabbable
elements throughout the entire modal (including both buttons and input fields),
pass the complete tabbable elements list to trapFocus instead of just buttons,
and set initial focus to the first tabbable element when each modal variant
(openChoice and openPicker) opens. Ensure the trapFocus function logic properly
cycles focus between the first and last tabbable elements to prevent escape.
- Around line 34-35: The code directly removes the DOM element of the previous
modal (openVeil) without invoking its proper cleanup path, which leaves the
modal's promise unresolved and key listeners active. Instead of calling
openVeil.remove() directly, you need to trigger the prior modal's cleanup or
finish method to ensure its promise is resolved and all listeners are properly
unregistered before removing the DOM element. This applies to all instances
where openVeil is removed without going through the modal's completion/closure
flow (including the locations at lines 64-70, 95-96, and 156-162).
- Around line 32-82: The openChoice and openPicker functions exceed the 50-line
maximum enforced by the max-lines-per-function ESLint rule. Refactor both
functions by extracting helper functions to reduce their complexity. For
openChoice, extract the dialog element creation logic (el calls and setAttribute
calls), the button creation and event setup logic (the choices.map block), and
the keyboard event handler setup into separate helper functions like
createDialogElement, createChoiceButtons, and setupKeyboardHandlers. Apply
similar refactoring to openPicker to break down its structure. Ensure each
extracted function has a single responsibility and that both openChoice and
openPicker end up below 50 lines by delegating setup tasks to these helper
functions.

In `@examples/web_ui_demo/js/popover.js`:
- Around line 21-103: The initRefPopover function exceeds the 50-line maximum
limit at approximately 83 lines. Extract the event listener setup code into
separate helper functions to reduce the main function size. Specifically, create
separate functions to handle the event wiring for the prev and next button
clicks, the popover mouse events (mouseenter and mouseleave), and the
document-level mouseover and click event listeners. Keep the state variables
(anchor, hideTimer) and core helper functions (sheetLinks, updateCounter,
showAt, hide, scheduleHide, step) within the main function, but move all the
addEventListener calls to separate, well-named functions that are called from
initRefPopover to improve readability and comply with the size constraint.

In `@examples/web_ui_demo/js/tree.js`:
- Around line 1-540: The file tree.js exceeds the 400-line limit and contains
functions that violate the 50-line maximum. Refactor by extracting the citation
walking and footer rendering logic into separate modules: create a dedicated
module for citation walk functionality (containing `walkToCitation`,
`pulseOnArrival`, `locateLink`, and related helpers) and move the footer
rendering concerns (the `renderRefsFooter` and `makeSectionChip` functions which
handle reference display) into their own module. Similarly, extract the inline
edit and delete affordances (the `makeParaActions`, `beginEdit`, `commitEdit`,
and related helper functions like `autosize` and `removedReferences`) into a
separate editing module. This will reduce tree.js to core rendering concerns and
bring all functions, including `renderSpecSheet`, under their respective size
limits.
- Around line 363-368: Remove the console.warn statement that logs the SpecR
citation walk warning message in tree.js. The console.warn call violates the
no-console ESLint rule which is configured as an error for all JavaScript and
TypeScript files in the project. Delete the entire console.warn block including
the conditional message that checks for bodyHasPattern.

In `@examples/web_ui_demo/js/web.js`:
- Around line 75-168: The renderWeb function exceeds the maximum allowed 50
lines per function. Refactor by extracting the arc rendering logic (the loop
iterating over model.edges) into a separate helper function, and extract the
node rendering logic (the loop iterating over model.nodes) into another separate
helper function. Additionally, extract the position and dimension calculation
logic into a separate helper function. Keep the main renderWeb function as an
orchestrator that handles the empty state check, calls these helper functions,
and appends elements to the canvas.
- Around line 59-63: When an edge already exists in the edgeMap and you
increment the count, you must also update the status field to preserve the
broken precedence. In the if (existing) block, after incrementing
existing.count, update existing.status to ensure that if either the existing
edge or the new edge has a broken status, the aggregated edge's status reflects
that it is broken. This ensures that the broken state is never lost when
aggregating duplicate edges.

In `@examples/web_ui_demo/server.mjs`:
- Around line 44-47: The readRequestBody function buffers the entire incoming
request body without any size limit, which can cause memory exhaustion with
large uploads. Add a maximum buffer size limit to the function by tracking the
total bytes read as chunks are accumulated in the loop, and either stop reading
or return undefined once the limit is exceeded. This prevents unbounded memory
growth when processing large request bodies.
- Around line 82-83: The decodeURIComponent call on the line that assigns the
pathname variable executes outside of your try-catch block and can throw an
error when the URL contains invalid escape sequences, causing unhandled
exceptions. Wrap the decodeURIComponent call in a try-catch block to handle
malformed URL encoding gracefully, ensuring that invalid URLs return a
controlled error response to the client instead of crashing the request handler.

In `@examples/web_ui_demo/Start-SpecR.bat`:
- Around line 5-9: The issue is that the `pause` command on line 8 overwrites
the `%errorlevel%` variable, causing the batch script to exit with a success
code even when startup fails. To fix this, save the error level value in a
variable before calling `pause`, then use that saved value to exit the script
with the correct error code. Capture `%errorlevel%` into a temporary variable
immediately after detecting the failure condition in the if statement, then
after the `pause` command completes, exit with that saved error code using the
`exit /b` command with the saved variable reference.

---

Nitpick comments:
In `@examples/web_ui_demo/scripts/windows/Start-SpecR.ps1`:
- Around line 43-56: The script starts the API process with Start-Process but
immediately proceeds to display connection information and launch the web UI
without verifying the API is ready to accept requests. Add a health check wait
loop after the Start-Process line that launches the API node process. This loop
should poll the API endpoint at http://127.0.0.1:$ApiPort with a reasonable
timeout and retry interval to confirm the API is responding before allowing the
script to continue with the Write-Host statements and the Start-Process call
that opens the web UI demo.

In `@examples/web_ui_demo/Start-SpecR.sh`:
- Around line 30-38: The Start-SpecR.sh script starts the API process with `node
dist/index.js &` and assigns the PID to API_PID, but immediately launches the
demo server with `node server.mjs` without waiting for the API to be ready,
causing potential race conditions. After the API_PID assignment and before the
cd command that precedes launching the demo server, add a polling loop that
checks if the API is ready to accept requests (you can use curl or similar to
probe the API port specified by API_PORT) with a reasonable timeout and retry
count to ensure the API is listening before proceeding to start the demo server.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 98628c34-77fe-49a3-be5c-ff7cbd49d4f0

📥 Commits

Reviewing files that changed from the base of the PR and between d6ef48c and ae6a066.

📒 Files selected for processing (17)
  • examples/web_ui_demo/.gitattributes
  • examples/web_ui_demo/README.md
  • examples/web_ui_demo/Start-SpecR.bat
  • examples/web_ui_demo/Start-SpecR.sh
  • examples/web_ui_demo/css/app.css
  • examples/web_ui_demo/index.html
  • examples/web_ui_demo/js/api.js
  • examples/web_ui_demo/js/app.js
  • examples/web_ui_demo/js/coordination.js
  • examples/web_ui_demo/js/dropzone.js
  • examples/web_ui_demo/js/labels.js
  • examples/web_ui_demo/js/modal.js
  • examples/web_ui_demo/js/popover.js
  • examples/web_ui_demo/js/tree.js
  • examples/web_ui_demo/js/web.js
  • examples/web_ui_demo/scripts/windows/Start-SpecR.ps1
  • examples/web_ui_demo/server.mjs

Comment thread examples/web_ui_demo/css/app.css
Comment thread examples/web_ui_demo/js/api.js
Comment thread examples/web_ui_demo/js/app.js
Comment thread examples/web_ui_demo/js/app.js
Comment thread examples/web_ui_demo/js/app.js Outdated
Comment thread examples/web_ui_demo/js/web.js
Comment thread examples/web_ui_demo/js/web.js
Comment thread examples/web_ui_demo/server.mjs
Comment thread examples/web_ui_demo/server.mjs Outdated
Comment thread examples/web_ui_demo/Start-SpecR.bat Outdated
thewrz and others added 2 commits June 18, 2026 17:09
Five lint-independent correctness/robustness fixes surfaced by CodeRabbit
review of PR #225. The size/console/stylelint findings are declined on the
PR: examples/ is intentionally outside the eslint/prettier surface
(pnpm lint targets src/ only) and the repo ships no stylelint.

- web.js: when the same (from→to) citation recurs, the most severe status
  now wins the arc colour (STATUS_RANK) so a broken ref isn't masked by an
  earlier resolved one.
- server.mjs: guard decodeURIComponent against malformed escapes (e.g. "%ZZ")
  and return 400 instead of throwing before the handler's try block.
- modal.js: opening a second modal now runs the prior modal's finish(null)
  instead of bare-removing its veil, so its capture-phase keydown listener is
  detached and its promise resolves (was an orphaned-listener leak).
- app.js: commitTextEdit resyncs the board to server truth on partial failure
  rather than retrying on top of half-applied mutations.
- Start-SpecR.bat: capture and re-emit the powershell exit code so callers
  see startup failures (pause no longer clobbers %errorlevel%).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses both CodeRabbit body nitpicks on PR #225. The launchers started the
demo server immediately after backgrounding the API, racing boot and surfacing
avoidable "API unavailable" failures on first run. Both now poll
GET /health (up to ~30s) before continuing.

The PowerShell wait sits inside the existing try block so the finally still
stops the API process if readiness times out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thewrz

thewrz commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Handled the two CodeRabbit body nitpicks (no review thread to resolve):

  • examples/web_ui_demo/Start-SpecR.shfixed in bdae17f: added a GET /health poll loop (up to ~30s) after backgrounding the API, before launching the demo server.
  • examples/web_ui_demo/scripts/windows/Start-SpecR.ps1fixed in bdae17f: same health-wait, placed inside the existing try block so the finally still stops the API process if readiness times out.

The 17 actionable inline threads are all replied-to and resolved: 5 genuine logic/robustness fixes in 4bc6176 (web.js edge-status precedence, server.mjs decodeURIComponent guard, modal.js orphaned-listener leak, app.js commitTextEdit resync, Start-SpecR.bat exit code). The size/no-console/stylelint findings were declined — examples/ is intentionally outside the linted surface (pnpm lint targets src/ only; the repo ships no stylelint), which is the whole point of isolating the demo here.

@thewrz thewrz marked this pull request as ready for review June 19, 2026 01:19
The launchers hard-coded API 3000 / web 3001 and failed when another dev
server already held those ports (e.g. a local Next.js app on 3000). Both
Start-SpecR.sh and Start-SpecR.ps1 now probe from the desired port and
increment to the first free one, propagating the chosen API port to the bind
(PORT), the demo proxy (SPECR_API_BASE), and the health wait, and the chosen
web port to the demo server. The web port also skips the API port so they
never collide.

Port availability is tested by attempting to bind (Node net.createServer in
bash, TcpListener in PowerShell) — the same operation the servers perform —
which is more reliable than /dev/tcp (often compiled out of bash) or
ss/lsof/nc (not always installed). SPECR_PORT / SPECR_WEB_PORT still set the
starting point.

Verified end-to-end with 3000 occupied: API bumped to 3001, demo to 3002,
health wait passed, both endpoints served.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thewrz thewrz merged commit 3eb3c7b into main Jun 19, 2026
5 checks passed
@thewrz thewrz deleted the mockup branch June 19, 2026 01:56
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.

1 participant