Skip to content

Main - #1

Merged
erikson1970 merged 30 commits into
masterfrom
main
Sep 7, 2026
Merged

erikson1970 merged 30 commits into
masterfrom
main

Conversation

@erikson1970

Copy link
Copy Markdown
Owner

Pull Main into Master - then we'll

erikson1970 and others added 30 commits September 6, 2026 17:56
- Replace unrelated 'Portfolio template' theme (index.html, static/) with a
  minimal placeholder page.
- Remove stray unrelated files (HotelList.txt, chat_log.md).
- Move planning docs into docs/ (STATUS, DATA_MODEL, VISUALIZATION,
  IMPLEMENTATION_PLAN) matching the paths AGENTS.md/README.md already point to.
- Add source/, tools/, css/, js/, data/ scaffold per the recommended site
  structure, each with a short README stating its purpose.
- Add source/world_history_chart_dataset_v2.xlsx as the human-editable source
  of truth (198 Boxes, 83 SEAIs, 390 Population rows, 89 Regions rows;
  verified against docs/STATUS.md counts, no duplicate/orphan IDs found).
- Add ISSUES.md and TRACEABILITY.md, and document the branching /
  issue-tracking / milestone-council-review workflow in AGENTS.md.
- Tidy .gitignore (was a placeholder).
- Three-lens council review (data integrity, static-site architecture,
  process/docs consistency) found no gatekeeper issues.
- Fix process/docs reviewer's nits inline: ISSUES.md severity field now uses
  the declared enum consistently, every issue has an explicit Status field,
  file a new minor ISSUE-005 for the 6 Boxes rows with no start_year (benign,
  documented poster-illegibility gap).
- Clarify AGENTS.md milestone review process: define 'milestone branch',
  require a minimum of 3 review lenses including process/docs consistency,
  specify who reconciles findings and the expected report format.
- Ignore .serena/ entirely (tool-local metadata, was partially untracked).
- Record council verdicts in ISSUES.md and TRACEABILITY.md.
- Add tools/wh_data.py: shared workbook loader + validation, with an
  explicit ERROR (structural, fails build) vs WARNING (content-quality,
  reported only) severity split, documented back into docs/DATA_MODEL.md #8.
- Add tools/build_data.py: source/*.xlsx -> data/*.json, refuses to write
  output when any ERROR-level problem is found.
- Add tools/validate_data.py: same validation, report-only, no writes.
- Verified against the real workbook: 198/83/390/89 rows load cleanly,
  0 errors, 671 warnings (198+83 TODO URLs, 390 unfilled population rows --
  all expected per docs/STATUS.md).
- Verified the failure path against a deliberately corrupted copy of the
  workbook (duplicate box_id, invalid color_hex, cascading FK break):
  build exits 1 and writes nothing.
- Commit generated data/boxes.json, data/seais.json, data/population.json,
  data/regions.json -- the static site has no build step in production, so
  these are checked in like any other build artifact.
- File ISSUE-006 (no automated test suite yet) and update TRACEABILITY.md
  with Milestone 1's requirement closure table.
Three-lens review (data integrity, static-site architecture/constraints,
process & documentation consistency) completed with no gatekeeper findings:

- Data integrity: PASS
- Architecture/constraints: PASS
- Process/docs: PASS_WITH_MINOR_ISSUES

Fixed inline:
- ISSUE-005 was misfiled under '## Resolved' despite its own Status field
  saying Tabled since Milestone 0; moved to '## Tabled'.
- tools/README.md now points at tools/requirements.txt instead of a bare
  'pip install openpyxl'.
- docs/STATUS.md refreshed to mention the build/validation pipeline.

Filed as tabled minor issues (not gatekeepers):
- ISSUE-007: dead code in wh_data.py's vocabulary-check loop
  (estimate_method branch can never fire).
- ISSUE-008: malformed/corrupt workbook produces a raw Python traceback
  instead of a clean CLI error (fails safely either way).

TRACEABILITY.md and ISSUES.md updated with the Milestone 1 council review
record.
Council review: data integrity PASS, architecture/constraints PASS,
process/docs PASS_WITH_MINOR_ISSUES. No gatekeeper findings. See ISSUES.md
and TRACEABILITY.md for details.
A genuinely interactive first slice: load the generated JSON, list all
Boxes sorted by start year with color swatches, click to select and see
an inspector panel (name, dates, region, span, confidence, notes, URL,
linked SEAIs), and filter the list by region_group.

- js/data.js: loadAllData() fetches data/*.json in parallel and builds
  boxesById/seaisByBoxId/populationByBoxId indices; regionGroups() for
  the filter control.
- js/state.js: createStore(), a minimal pub-sub state container with the
  field shapes from docs/IMPLEMENTATION_PLAN.md Phase 2.
- js/app.js: wires data + state + DOM rendering; no framework, plain ES
  modules per AGENTS.md.
- css/history.css: minimal responsive styling; light/dark via
  prefers-color-scheme; selection state carries a checkmark + bold text,
  not just background color (AGENTS.md accessibility: color isn't the
  sole encoding); list items are real <button>s so click/keyboard both
  work with no hover-only interaction.
- index.html: real layout (header/status/summary, region filter, box
  list + inspector panels, footer), loads js/app.js as an ES module.

Runtime-verified with Node (built-in fetch, v18.15.0) against a local
static server and a minimal DOM stub (no browser available in this
environment): confirmed loadAllData/regionGroups against the real
data/*.json, createStore's subscribe/set/unsubscribe semantics, and the
full app.js wiring end-to-end (initial render shows 198 boxes/83
SEAIs/390 population rows/89 region rows, 13 region-filter options,
click-to-select renders the correct inspector content, region filter
narrows the list and clears selection). node --check passed on all three
new JS files (via temporary .mjs copies, since this repo has no
package.json declaring module type).
Marks V1-1/V1-2/V1-5/V1-6/V1-7 Met against the Milestone 2 slice; V1-3/V1-4
(Sankey/alluvial, timeline) remain Not started (Phase 3/4). Council review
for Milestone 2 pending.
Three-lens review completed with no gatekeeper findings:
- Data integrity: PASS_WITH_MINOR_ISSUES
- Architecture/constraints & accessibility: PASS
- Process/docs: PASS_WITH_MINOR_ISSUES

Fixed inline:
- js/app.js: filteredBoxes sorted the 6 null-start_year boxes (ISSUE-005)
  by their *end* year, misplacing prehistoric-era boxes (e.g. an
  Indigenous Era box ending 1607) next to 16th-century boxes instead of
  near other prehistoric-start entities. Sort fallback changed from
  '?? end_year ?? 0' to '?? Number.NEGATIVE_INFINITY'; re-verified with
  the same Node/fake-DOM harness that the 4 prehistoric-start boxes now
  sort to the front of the list.
- js/app.js: main() had no .catch(), so a rendering-time error (as
  opposed to an already-handled fetch failure) would only surface as a
  silent unhandled-rejection warning. Added main().catch(...) routing to
  the same error-display path.
- js/README.md: rewritten to describe the actual data.js/state.js/app.js
  split instead of a since-superseded planned module list.
- docs/STATUS.md: refreshed to mention the Phase 2 prototype.

Filed as tabled minor issues (not gatekeepers):
- ISSUE-009: no automated test suite for js/app.js|data.js|state.js
  (parallel to ISSUE-006's Python-side gap).
- ISSUE-010: milestones have landed as one or two large commits rather
  than the frequent-small-commits granularity AGENTS.md describes.

TRACEABILITY.md and ISSUES.md updated with the Milestone 2 council review
record; M2-9 downgraded from Met to Partial pending ISSUE-009.
Council review: data integrity PASS_WITH_MINOR_ISSUES, architecture/
constraints & accessibility PASS, process/docs PASS_WITH_MINOR_ISSUES. No
gatekeeper findings; a real sort-order bug and an unhandled-rejection gap
were fixed inline before merge. See ISSUES.md and TRACEABILITY.md.
Pure file move (git detects all as renames, no content changes yet). The
whole tree moves together so internal relative paths (index.html -> css/js,
js/app.js fetching data/, tools scripts reading source/) are unaffected;
only external pointers into this project (AGENTS.md, README.md) need
updating in a follow-up commit.

Part of the site restructure: root becomes a personal landing page,
projects/<name>/ holds each project. See docs/IMPLEMENTATION_PLAN.md and
the Milestone 3 TRACEABILITY.md entry (added in a later commit).
- New root index.html + css/site.css: modest personal landing page for
  erikson1970, Viking/Nordic/French-themed (navy/forest-green, gold/bronze
  accents, serif display headings, CSS-only knotwork divider, no image
  assets). Uses user-provided bio copy verbatim. Links to
  projects/world-history/ and is designed for future project cards.
- Root README.md rewritten for the personal-site/multi-project framing;
  old world-history-specific README.md content preserved at
  projects/world-history/README.md with a historical note about the move.
- AGENTS.md: new "Repository structure" section; all project-specific
  paths (docs/, source/, tools/, css/, js/, data/) updated to
  projects/world-history/; milestone-branch definition extended to cover
  site-level changes.
- ISSUES.md/TRACEABILITY.md: kept shared at root per user's explicit
  choice (one continuous history, not split per project); both given a
  "Path note" explaining pre-Milestone-3 entries reference now-superseded
  root-relative paths rather than rewriting history.
- projects/world-history/tools/README.md: clarified that build/validate
  script default paths are relative to the project directory, so they
  must be run with projects/world-history/ as the working directory.
- TRACEABILITY.md: added Milestone 3 requirements table (M3-1..M3-7), all
  Met, evidence includes the local-server/curl/Node/validate_data.py
  verification already performed against the moved project tree.

Council review pending before merge to main.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three lenses (path/link integrity, architecture/accessibility,
process/docs consistency) reviewed commit ce8b234. No gatekeeper findings.

Fixed inline:
- Wrapped project-card links in <h3> (index.html, css/site.css) so
  screen-reader heading navigation works once more project cards are added.
- Added missing fonts.googleapis.com preconnect alongside fonts.gstatic.com.
- Extended TRACEABILITY.md's Path note to cover README.md section
  citations (Milestones 0-2 cite headings that moved wholesale to
  projects/world-history/README.md in Milestone 3).
- Downgraded TRACEABILITY.md M3-4 from Met to Partial: the Node
  loadAllData() verification was never captured as a committed, repeatable
  test, same gap as ISSUE-009 (extended that issue's description to note
  the recurrence).

Noted, not newly filed: Milestone 3 again landed as two large commits,
consistent with already-tabled ISSUE-010.

Council review record added to ISSUES.md and TRACEABILITY.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Root becomes a personal landing page for erikson1970; World History
Explorer moves to projects/world-history/. Council-reviewed, no
gatekeeper findings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a 7-row hand-authored Links sheet to the source workbook covering
the Mediterranean/Europe subset from docs/IMPLEMENTATION_PLAN.md Phase 3
(Roman -> Western Roman/Byzantine, Frankish -> West/East Francia,
West Francia -> Kingdom of France, East Francia -> Holy Roman Empire,
Byzantine -> Ottoman). Each row has a real Wikipedia source_url, High
confidence, and substantive notes.

Extend tools/wh_data.py with validate_links(): checks link_id
uniqueness, source/target FK existence, malformed year, and
relation_type membership -- classified as ERROR (not the usual
WARNING-for-vocabulary default) per IMPLEMENTATION_PLAN.md Phase 1's
explicit "fails on unknown relation types" requirement. Year-outside-
span is a WARNING (content-quality, not structural). Wire into
build_data.py as a new links.json output.

Verified: validate_data.py runs clean (671 warnings, 0 errors,
unchanged from before -- Links adds 0 new warnings), build_data.py
writes links.json with all 7 rows correctly, and a deliberately
corrupted relation_type / broken FK correctly blocks the build (ERROR,
exit 1, no output written).
Add js/sankey.js: buildSankeyFigure(links, boxesById) is a pure data
transform (node x from start_year, clamped [0.02, 0.98]; arrangement:
"fixed" to strictly enforce chronological left-to-right node order
per AGENTS.md Visualization semantics -- no auto-layout reordering).
renderSankey(container, data, callbacks) wraps Plotly.newPlot and
plotly_click handling, routing node clicks to onSelectBox and link
clicks to onSelectLink.

Wire into js/app.js: selectedLinkId added to js/state.js's
initialState so a clicked link drives the same inspector panel as a
clicked box (mutually exclusive -- box-list clicks and region-filter
changes clear selectedLinkId and vice versa). renderSankey() is called
inside a try/catch so a Plotly load failure (offline, CDN blocked)
degrades to a text fallback in the alluvial container without
breaking the box list/inspector/filter.

js/data.js: load links.json, build linksById and linksByBoxId
indices.

index.html: load Plotly 2.35.2 from cdn.plot.ly (pinned exact
version, reachability verified), add the alluvial section with a
role=img/aria-label container and a note that ribbon width is
placeholder equal-weighting, not population-based (see
docs/VISUALIZATION.md, ISSUE-003).

Verified via a Node fake-DOM harness (fetch redirected to a local
static server, no window.Plotly to exercise the fallback path): data
loads, 198 boxes render, region filter (13 options) narrows the list
correctly, box click populates the inspector, the alluvial container
falls back to a caught error message as designed. Real-browser
Plotly rendering/click-interaction is not verifiable in this
environment -- see TRACEABILITY.md M4 for the resulting Partial
caveat.
Add the Milestone 4 section to TRACEABILITY.md (M4-1 through M4-10),
covering IMPLEMENTATION_PLAN.md Phase 3's acceptance criteria and
retroactively covering Phase 1's Links validation requirement (M1-6,
previously Not started). M4-6/M4-8 marked Partial: the pure data
transform, pipeline, and non-Plotly UI wiring are verified, but real
browser/Plotly click-interaction can't be confirmed in this
environment. Update the v0.1 definition-of-done table (V1-3, V1-5).

Add a Milestone-4 note to ISSUES.md's ISSUE-004 (partial resolution
for Links, BoxSegments still fully open) without rewriting the
original entry, per the historical-record-integrity precedent from
earlier milestones.

Refresh docs/STATUS.md (Phase 3 alluvial prototype described, Links
recommended-work item marked done-for-a-subset, succession-not-modeled
limitation updated) and js/README.md (document sankey.js).
Three-lens review (data integrity, static-site architecture/accessibility,
process & docs) all PASS_WITH_MINOR_ISSUES, no gatekeeper findings.

Fixed inline:
- Accessibility gap: the Plotly widget alone gave keyboard/AT users no
  way to browse or select a link. Added #alluvial-list, a visually-hidden
  (visible on :focus-within) list of real <button>s -- one per link --
  that call the same onSelectLink path as a chart click. Verified via the
  fake-DOM harness: 7 items render, clicking one populates the inspector.
- Theme bug: the Sankey chart ignored the page's dark mode (Plotly
  defaults to opaque white). renderSankey() now reads --bg/--fg off
  :root and sets paper_bgcolor/plot_bgcolor/font.color.
- Robustness: links.json was fetched in the same Promise.all as the
  required tables, so a broken Links file could take down the whole
  page. Split into required (hard-fail) vs. optional (catch, default
  to [], warn) fetches in js/data.js.
- Latent NaN node-position bug if a future subset had no box with a
  numeric start_year (Math.min/max of an empty array, -Infinity || 1
  still -Infinity); now guarded.
- Un-awaited Plotly.newPlot promise: an async rejection would bypass
  the intended fallback message. renderSankey() is now async and
  awaited by its caller inside the existing try/catch.
- Misleading alluvial-note prose that read as one chained succession;
  reworded to list the 7 discrete links.
- JSDoc gap (linksById missing from loadAllData's comment).
- Opaque validation message for a missing relation_type ('relation_type
  None not in [...]' -> 'missing relation_type').

Tabled as a new issue: ISSUE-011 (DATA_MODEL.md's 'transition year
sensible' check is a loose union-envelope heuristic). Extended existing
entries: ISSUE-009 (third recurrence of uncommitted runtime JS
verification), ISSUE-010 (commit granularity improved to 3 commits this
milestone). Reworded TRACEABILITY.md M4-6/M4-7/M4-8 so the Partial
ratings don't overclaim relative to what the fake-DOM harness actually
exercised vs. what still needs a real browser.
- js/timeline.js: buildTimelineLayout() (pure, Node-testable) + renderTimeline()
  (D3 SVG). One bar per in-view box, ordered chronologically, colored by
  color_hex, with diamond SEAI markers and a year axis.
- Year-range (From/To year) and Show SEAIs controls wired to js/state.js's
  existing yearStart/yearEnd/showSeais fields.
- The 6 ISSUE-005 boxes with no legible start date get a dashed left edge
  clamped to the current view's left boundary, not a fabricated date.
- Deliberately no independent hidden-list fallback for the timeline: every
  box it draws is already keyboard-selectable via the existing box list.
- D3 7.9.0 loaded via pinned cdnjs CDN script, same pattern as Plotly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixed inline: reversed year-range bug (js/app.js now rejects yearStart >
yearEnd before it reaches the store), dead SEAI tooltip (pointer-events:none
on .seai-marker), wrong evidence numbers/citations in TRACEABILITY.md,
imprecise code comment in timeline.js.

Closed ISSUE-009's would-be 4th recurrence with an actual fix instead of
re-tabling: js/timeline.test.mjs is a committed, repeatable test for
buildTimelineLayout(), enabled by a new root package.json declaring
type:module (browser loading via index.html is unaffected).

Backfilled a missing 'Milestone 4' section in ISSUES.md (referenced by
TRACEABILITY.md and commit 09eea5f but never actually written), added the
Milestone 5 section, filed ISSUE-012 (timeline resize + a few
prehistoric-SEAI edge cases), annotated ISSUE-003/005/010, and superseded
V1-4 in TRACEABILITY.md's v0.1 table.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… traceability

User-authored docs/timescaleRequirement.md specifies a nonlinear year->x
mapping for the timeline (recent history gets more horizontal room in wide
views, relaxing toward linear as the view narrows), shared across every
time-based element in a view and user-tunable via a [-2,2] slider.

Not implemented yet -- this commit only records the requirement:
- docs/IMPLEMENTATION_PLAN.md: new Phase 5.5, ordered before Phase 6 since
  population knot points need the same scale; notes it revisits Phase 3's
  Sankey and Phase 4's timeline x-positioning (both currently linear).
- TRACEABILITY.md: new 'Planned -- Phase 5.5' section, TS-1..TS-10.
…x-list)

Selecting a box or link anywhere on the page now updates every panel:
- js/sankey.js: applySankeySelection() re-colors the already-rendered
  Sankey trace via Plotly.restyle (node outline for a selected box, link
  color for a selected link) without re-registering plotly_click.
  computeSankeyHighlight() is the pure, Node-testable split of that
  computation, matching buildSankeyFigure/buildTimelineLayout's existing
  pattern. renderSankey() now resolves {boxIds} so app.js can drive this.
- js/timeline.js: renderTimeline() accepts state.linkHighlightBoxIds, an
  optional Set marking a selected link's two endpoint boxes with a new
  .link-endpoint class (distinct, non-dashed, from .approx-start).
- js/app.js: renderAlluvialListSelection() toggles .selected/aria-pressed
  on the alluvial-list buttons (mirroring the box list's own pattern);
  renderTimelineView() resolves selectedLinkId to its endpoint box_ids;
  renderSankeySelection() wires applySankeySelection() into the store
  subscription, plus a catch-up call right after renderSankey() resolves
  in case a selection happened elsewhere while Plotly was still loading.
- css/history.css: #alluvial-list button.selected (background + bold +
  checkmark, not color alone) and #timeline .timeline-bar.link-endpoint.
- js/sankey.test.mjs: new committed test (buildSankeyFigure, previously
  untested, plus computeSankeyHighlight) -- 20 assertions, all passing.

Deep-link URL query params (the plan's own 'possible future' example) are
explicitly out of scope for this milestone -- see TRACEABILITY.md M6-6.

Also: docs/STATUS.md, js/README.md, index.html notes, and TRACEABILITY.md
Milestone 6 (M6-1..M6-9) updated to describe the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Accessibility gatekeeper: Sankey selected-link was color-only encoded
(ribbon color, no other channel). computeSankeyHighlight() now also
outlines the selected link's two endpoint nodes via node.line, same
mechanism a selected box already used. Tests updated accordingly.

Architecture gatekeeper: ISSUES.md was missing a Milestone 6 note on
ISSUE-009 despite js/sankey.test.mjs and TRACEABILITY.md both pointing
at it. Added.

Minor fixes applied inline: .link-endpoint CSS now resets
stroke-dasharray so it reads solid even on an .approx-start box;
TRACEABILITY.md's status vocabulary now lists "Deferred"; js/README.md's
sankey.js paragraph now mentions sankey.test.mjs.

TRACEABILITY.md and ISSUES.md gain the full Milestone 6 council-review
write-up (3 verdicts, findings-and-fixes), matching the Milestone 4/5
pattern.

Verified: node --check on every changed .js file, and both
js/sankey.test.mjs (23/23) and js/timeline.test.mjs pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sync)

Phase 5: selecting a box or link anywhere (box list, alluvial-list,
Sankey diagram, timeline) syncs the shared selection state and reflects
it across every panel. Extends the pure/DOM-split pattern from prior
milestones (computeSankeyHighlight/applySankeySelection).

Also folds in docs/timescaleRequirement.md (user-authored) as Phase 5.5
in docs/IMPLEMENTATION_PLAN.md, and records it (TS-1..TS-10) in
TRACEABILITY.md as documentation only, not yet implemented.

Full 3-lens council review run; both gatekeeper findings (Sankey
selected-link color-only encoding, missing ISSUES.md note) fixed before
merge. See TRACEABILITY.md's Milestone 6 section for details.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New js/timescale.js: a pure, Node-testable semanticTimeScale({tMin, tMax,
scaler}) implementing docs/timescaleRequirement.md's nonlinear mapping
(x = 1 - (a/M)^p, p = 1/(1 + (M/3000)*2^s), the multiplicative form the
doc requires) plus calendar-time-first timeTicks(). No D3/DOM/Plotly
dependency, so both the timeline and the Sankey diagram can share one
scale instance without either depending on the other's rendering stack.

js/state.js gains state.timeScale (the slider's `s`, [-2, 2], default 0).

js/timeline.js: buildTimelineLayout() now builds one shared scale from
[state.yearStart, state.yearEnd, state.timeScale] and uses it for every
box bar, SEAI marker, and axis tick -- no more per-element linear
normalization. renderTimeline() drops d3.scaleLinear/d3.axisBottom for
the new scale plus a hand-built axis (same .timeline-axis/.domain/.tick
CSS classes, so no styling changes needed), with explicit "N BCE"/"N CE"
tick labels instead of a raw signed number.

js/sankey.js: new computeSankeyNodeX(boxIds, boxesById, state) replaces
buildSankeyFigure's old per-subset min/max normalization with the same
shared scale and the app's global year range -- Sankey node x now lines
up with the timeline's x-axis exactly, per the spec's "Shared Scale"
list. buildSankeyFigure/renderSankey take a new `state` argument; a new
applySankeyTimeScale() (kept separate from the existing
applySankeySelection(), same split reasoning as buildSankeyFigure vs.
computeSankeyHighlight) re-positions nodes via Plotly.restyle when the
shared state changes. The node *set* is still driven only by
data/links.json, unfiltered by year range -- only node x changed.

js/app.js: new #time-scale slider wired to the `input` event (continuous
update while dragging, per the spec's "Zoom Interaction"), threading
state into renderSankey()'s call and adding a renderSankeyTimeScale()
subscriber alongside the existing renderSankeySelection().

index.html: adds the "Time compression (ancient detail <-> recent
detail)" range input; updates the timeline/alluvial notes and footer to
describe the now-shared, page-wide scale. css/history.css: sizes the new
range input.

Tests: js/timescale.test.mjs (new, 212 assertions covering the spec's
own Validation section -- boundary, monotonicity incl. out-of-domain
years, round-trip, zoom/slider behavior, coordinate stability, tick
generation). js/timeline.test.mjs and js/sankey.test.mjs extended for
the new scale/x0/x1/ticks fields and computeSankeyNodeX.

Docs: TRACEABILITY.md's "Planned -- Phase 5.5" placeholder becomes
"Milestone 7", TS-1..TS-11 (TS-11, optional smooth-animation-while-
zooming, Deferred -- logged as ISSUES.md ISSUE-013). docs/STATUS.md and
js/README.md updated to describe the new module and its integration.

Deferred (ISSUE-013): smooth animation while zooming/dragging -- framed
as optional in the source spec, not attempted this milestone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3-lens review of 887f483: Correctness/data-fidelity PASS, Architecture/
docs PASS, Accessibility/UX PASS_WITH_MINOR_ISSUES.

Gatekeeper fix: js/timescale.js's timeTicks() now excludes an exact-0
tick candidate -- js/timeline.js's formatTickYear was rendering it as
"0 CE", a year docs/DATA_MODEL.md documents doesn't exist in this
dataset's BCE/CE convention, and the app's own default -3000..2026 view
produced exactly such a tick. formatTickYear also independently guards
year 0 (and the -0 Math.round can produce) for any future reuse as a
hover/inspector label. New timescale.test.mjs assertions cover both a
straddling-zero span and a smaller-interval one.

Minor fixes: js/sankey.test.mjs now exercises computeSankeyNodeX's
null-start_year (ISSUE-005) fallback with a synthetic box; js/timeline.js's
tick rendering reuses buildTimelineLayout's precomputed x instead of
recomputing scale.yearToX(year) a second time per tick.

Tabled: ISSUE-013 (smooth zoom animation, optional per spec, already
filed) and new ISSUE-014 (time-scale slider's input listener re-renders
every panel, not just timeScale-dependent ones -- no debounce/RAF
throttle). ISSUE-010 (commit granularity) noted as regressed further
this milestone.

TRACEABILITY.md gains a "Milestone 7 council review" section; ISSUES.md
gains the matching "Milestone council reviews" write-up plus ISSUE-014
and Milestone-7 notes on ISSUE-010/ISSUE-013.

Milestone approved to merge to main.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ale)

Implements Phase 5.5 / docs/timescaleRequirement.md's semantic-zoom time
scale (TS-1..TS-10 in TRACEABILITY.md): a shared, pure semanticTimeScale()
(js/timescale.js) used by every time-based visual element -- timeline bars,
SEAI markers, axis ticks, and Sankey node x -- via a single -2..2 slider
(js/app.js/index.html) that recomputes continuously on the `input` event.

Highlights:
- js/timescale.js: semanticTimeScale({tMin, tMax, scaler}) implementing
  p = 1 / (1 + (M/3000) * 2^s), x = 1 - (a/M)^p; timeTicks() generates
  candidate tick years in calendar time first, then maps them through the
  shared scale (never the reverse).
- js/timeline.js, js/sankey.js: both now route all time-based x-positioning
  through one shared scale instance per render, per the spec's "Shared
  Scale" requirement.
- js/state.js, js/app.js, index.html, css/history.css: new timeScale state
  field and #time-scale slider control, continuous input-event updates.

Council review (3 lenses -- correctness/data-fidelity: PASS; accessibility/
UX: PASS_WITH_MINOR_ISSUES; architecture/docs: PASS) found one gatekeeper:
a calendar-arithmetic tick landing on year 0 rendered as "0 CE", which isn't
a real year under this dataset's BCE/CE convention (docs/DATA_MODEL.md:
"There is no attempt to model a historical year zero"). Fixed in
timeTicks() itself, with js/timeline.js's formatTickYear also hardened
defensively. Two other minor findings were fixed inline (untested
null-start_year Sankey fallback; redundant yearToX recompute in tick
rendering); two more were tabled as ISSUE-014 (new) and a note on
ISSUE-010, per the same reconciliation process as prior milestones. Full
writeup: TRACEABILITY.md "Milestone 7 council review", ISSUES.md
"Milestone 7 -- Semantic zoom time scale (Phase 5.5)".

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@erikson1970
erikson1970 merged commit dbdfff5 into master Sep 7, 2026
3 checks passed

This branch was successfully deployed

1 active deployment
github-pages e1051ab6 Deployed Sep 7, 2026 by erikson1970 via deploy #11
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