App.jsx owns sources/sourceId/versions/versionId and persists them
across reloads. StatusBar renders the dropdowns plus a status badge —
Source-only on Setup, Source · Version · status on Baseline/Forecast.
The duplicate in-view selector bars in Forecast and Baseline are gone;
Baseline keeps its version actions (New/Close/Reopen/Delete) inline.
Setup reports source-list mutations up via refreshSources so the bar
stays in sync after register/deregister.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
App chrome now uses Pro Dark's neutral grays (#242526 background,
#2a2c2f panels, #4c505b borders) so the surrounding UI sits cleanly
against the viewer instead of clashing with its warmer tone. Status
accents are desaturated to match. Forecast view sets theme="Pro Dark"
or "Pro Light" on the perspective-viewer in sync with the toggle.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Perspective table is now created with index: 'pf_id'. Delete endpoints
return the pf_ids they removed; the client calls table.remove(pf_ids)
in undoEntry. Avoids the full /data refetch that dominated undo time.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pg now returns bigint/numeric as JS numbers so Arrow infers Int/Float64
instead of Dictionary<Utf8>. /data accumulates rows and emits a single
record batch to avoid dictionary REPLACEMENT messages that crash
Perspective's WASM reader. Forecast view streams the response body and
shows received/total bytes while loading. Drops stale public/ static
middleware that was shadowing the React build at /.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Server streams rows from a pg cursor in 10k-row batches, building Arrow
record batches incrementally and piping them as chunked HTTP response —
Node.js heap stays bounded regardless of dataset size.
Client fetches as arrayBuffer() and loads directly into Perspective worker
(native Arrow path, no JSON deserialization). X-Row-Count header drives
a non-blocking banner for datasets >= 500k rows. validCols now derived
from col_meta rather than from row keys.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Re-applies fetchCurrentTotals dimension-only filter (prevents Perspective errors
from split_by/expression columns in the filter), toolbar three-group reorganization
(Layout | Expand | Data with dividers), always-visible Save as…, msg in toolbar,
resizable panel, change log modal with undo and inline note editing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Expression columns (bucket, computed) are defined in cfg.expressions and
are valid pivot axes, but weren't in validCols (raw table columns), so
they were filtered out of group_by/split_by on every layout restore.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /api/versions/:id/log — log entries with row counts via JOIN
- DELETE /api/log/:logid — undo in a transaction (delete fc rows + log entry)
- PATCH /api/log/:logid — update note text
- History button opens a modal: op badge, slice, editable note, row count, Undo per entry
- Undo triggers full Perspective table reload via initViewer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix perspective-click handler to use event filter triples instead of
__ROW_PATH__ — Perspective encodes row position as [col,'==',val] in
detail.config.filter
- buildWhere now skips unrecognised slice keys (e.g. pf_iter) instead of
throwing, so only dimension columns reach the WHERE clause
- Add draggable resize handle on the operation panel (160–480px)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Baseline.jsx: merge Reference section into Add Segment form with baseline/reference toggle; segment rows now clickable to expand stored WHERE clause + timeline; date filter inputs use type="date" for date-role columns
- Timeline.jsx: add type prop ('baseline'|'reference'); reference band uses purple; single-band height shrinks to 52px; canvas uses requestAnimationFrame to fix offsetWidth=0 on mount
- operations.js: reference route now accepts where_clause like baseline (drops date_from/date_to)
- sql_generator.js: reference SQL template uses {{filter_clause}} instead of hardcoded BETWEEN
Note: existing sources need Generate SQL re-run to pick up the new reference template.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ui/: React + Vite + Tailwind app (Setup, Baseline, Forecast views, collapsible sidebar, status bar, canvas timeline)
- server.js: serve built UI from public/app/
- package.json: add build script (cd ui && npm run build)
- routes/sources.js: default new col_meta role to 'dimension' instead of 'ignore'
- .gitignore: exclude public/app/ build output
- pf_spec.md: update tech stack, nav, frontend section, and project status to reflect current implementation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>