Skip to content

spaces: a timeline and a workload chart, as view layouts - #443

Open
nyblnet wants to merge 1 commit into
mainfrom
spaces-gantt
Open

spaces: a timeline and a workload chart, as view layouts#443
nyblnet wants to merge 1 commit into
mainfrom
spaces-gantt

Conversation

@nyblnet

@nyblnet nyblnet commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Two new shapes on the view layout control, branched off origin/main and
depending on none of the unmerged feature branches.

  • gantt — one bar per page, from its start date to its due date. Today
    marked; overdue work outlined; bar colour from the grouping field's option.
  • workload — the first vt:"number" field in the schema, summed per
    bucket, drawn by kernel/src/charts.ts (read-only import, the path dash
    already uses). The bucket is groupBy, defaulting to the person field.

Both read the view's existing source, filter, sort and groupBy, so
{"layout":"workload","source":{"under":"<page>"},"filter":{"open":true}} is
"the workload for this project, open only" with no new key anywhere.

The chart host: a layout, not a chart block

Argued in spaces/src/gantt.ts and recorded in docs/DECISIONS.md, because
every chart this app grows inherits it.

  1. Both shapes need source/filter/sort/groupBy and nothing else. A
    chart block would grow four keys with those same four meanings — two
    vocabularies for one question, permanently, in every file ever saved.
  2. The degradation is better and it was measured. A shell built from
    origin/main, shown {"type":"view","layout":"gantt"}, renders a board
    of the same pages, keeps layout:"gantt" through a save, and keeps the new
    start prop blocks — checked in a browser against a real build. A chart
    block falls to the unknown-type path and renders its html: one line of text
    where a schedule was.
  3. The honest counter-argument is in the source and recorded rather than
    dismissed: a workload chart really is an aggregate. It loses on scope — the
    aggregation is a property of the output while every input key keeps the
    meaning it had. A chart of data that is not pages (a table block's
    numbers) should be its own block type, and this is not a precedent against
    that.

start, and why absent start is not an edge case

The tracker shipped with due and nothing else. Every issue in every file
already written therefore has a due date and no start
— so those draw as a
milestone diamond at the due date, not a zero-width bar. Symmetric: a start
with no due is a diamond too. Neither date at all: left out, and counted.

Wrong data, decided rather than left to arithmetic

case what it does
due before start drawn between the two dates that are really in the file, flagged — never silently swapped
malformed date (2026-13-99, prose, an object) an absent date
negative / NaN / Infinity estimate excluded from the sum and counted — a −3 absorbed into a bar makes somebody look lighter than the work they hold
10,000-page view capped at 300 bars / 20 buckets, deterministically ordered, and it says what it left out; the workload tail is folded, so the visible bars still sum to the document
prototype-shaped bucket names (__proto__, toString) a Map tally, no prototype chain to fall through

Every date comparison is an integer day number computed arithmetically
(days-from-civil); no Date is constructed for any comparison.

Print and the static preview

Colour and geometry travel as presentation attributes inside the SVG, not in
styles.css — which the file-manager still's own stylesheet is not. Measured:
disabling the app stylesheet leaves the picture identical in colour and in every
proportion (uniform 1.228× rescale from the container width alone), and both
charts arrive in the serialized [data-bento-preview] host with their fills,
fitting a 300px thumbnail box with no overflow.

Not built, and what is left room for

Dependencies and critical path. Both need a typed page reference, which
exists only on the unverified spaces-relations branch. No field invented, no
second way to express a page reference. What is left for them at zero format
cost: GanttModel.bars is a flat list keyed by pageId, each carrying its
resolved x/w on a shared 0..1 axis — an arrow is two lookups and two
numbers, a critical path is a walk over the same list. The edges will live on
the pages, not on the view block, so neither needs this module or the format to
change shape.

Verification

  • node scripts/test-spaces.mjs — all 9 rigs pass, including the new
    scripts/test-spaces-gantt.ts (114 behavioural checks, registered in the
    runner and in ci.yml under a five-timezone matrix). Whole suite also green
    under TZ=Pacific/Kiritimati and TZ=Pacific/Niue.
  • tsc --noEmit clean; npm run build:single + shell-gate.mjs pass.
  • 11 sabotages, each restored from an in-memory copy: 11/11 red under
    TZ=Pacific/Kiritimati, 10/11 under TZ=UTC. The one that differs is the
    point — replacing the arithmetic day number with new Date(y, m-1, d) is
    invisible from inside UTC and off by a day in UTC+14.
  • Browser-verified on a uniquely-named copy over http://127.0.0.1, with a
    build-marker check on every probe: bar geometry read with
    getBoundingClientRect, SVG <a href="#p/…"> routing confirmed by an actual
    navigation, the full six-step layout cycle returning the block to a deleted
    layout key, and the workload Group button reading Group · Assignee rather
    than Group · Status.
  • Byte cost, origin/main and this branch built back to back: +13,304 B
    (277,769 → 291,073), of which +6,180 B is the shared chart engine, probed
    on its own. test-spaces-size.mjs reports +32,619 B against a drifted
    reference; that number is not the change.

Format additivity

layout:"gantt"/"workload" are stored words; board remains the absent
key, so a view cycled all the way round is byte-identical to one nobody touched
(verified in the browser). start is an ordinary optional prop block carrying
its own readable html. Nothing is repurposed and no docId is regenerated.

Notes for the reviewer

  • Touches two ops-surface files under the shared-surface rule, claimed by path:
    scripts/test-spaces.mjs (one RIGS entry) and .github/workflows/ci.yml (one
    step). Registration is not optional — the manifest check fails without both.
  • scripts/test-spaces-model.ts has two assertions that hardcoded
    nextLayout('gallery') === 'board'; the cycle is six long now and they were
    updated in place.
  • render.ts still has the duplicated, unreachable table branch tracked on the
    board as spaces-table-unreachable. Left alone — not this change's to fix.
  • Six is the ceiling for a cycling layout button; a seventh shape should convert
    that control to a picker. Written down in fields.ts at the site.

Two new shapes on the one layout control. `gantt` draws a bar per page from its
start date to its due date, with today marked and overdue work outlined;
`workload` sums the estimates per assignee into a bar chart drawn by the shared
kernel chart engine. Both read the view's existing source, filter, sort and
groupBy, so "the workload for this project, open only" needs no key of its own.

A chart is a LAYOUT and not a new block type, argued at length in
spaces/src/gantt.ts and recorded in docs/DECISIONS.md, because every chart this
app grows will inherit the choice. The measurable half: a shell built from the
previous release, shown a view block with layout:"gantt", renders a board of the
same pages, keeps the key through a save and keeps the new `start` values —
verified in a browser against a real origin/main build. A chart block would have
fallen to the unknown-type path and drawn one line of text where a schedule was.

Adds a `start` date field. Absent `start` is not an edge case but the entire
installed base — the tracker shipped with `due` alone — so those draw as a
milestone diamond at the due date rather than as a zero-width bar.

Wrong data is decided rather than left to arithmetic: a due date before the
start is drawn between the two dates that are really in the file and flagged,
never silently swapped; a malformed date is an absent date; a negative or
non-numeric estimate is excluded from the sum and counted; an oversized view
draws a capped, deterministically ordered chart that says what it left out.
Dates are integer day numbers computed arithmetically with no Date constructed
for any comparison — scripts/test-spaces-gantt.ts (114 checks) runs under five
timezones, and a sabotage swapping in a local Date passed under UTC and failed
under Pacific/Kiritimati.

Both charts print and both survive into the file-manager still: colour and
geometry travel as presentation attributes inside the svg rather than in
styles.css, which the still's own stylesheet is not.

Eighteen new strings in all eight catalogs, packed and confirmed in packed.ts.
Shell cost, main and this branch built back to back: +13,304 bytes, of which
+6,180 is the shared chart engine.
@github-actions

Copy link
Copy Markdown

Build size

main (0100083) → spaces-gantt (5008cce)

app base PR change
bento/slides 677.9 KiB 677.9 KiB 0.0 KiB (0.00%)
bento/spaces 271.3 KiB 284.3 KiB +13.0 KiB (+4.79%)
bento/dash 424.1 KiB 424.1 KiB 0.0 KiB (0.00%)

Updated: 2026-09-10T22:15:59Z

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