Uh oh!
There was an error while loading. Please reload this page.
Kanban, gantt and timeline lenses over the same task data - #49
Merged
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
…y stopgap plus product pins Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren
marked this pull request as ready for review
September 1, 2026 05:42
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Sep 1, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#12
Five new lenses over the existing rows —
board,schedule,recent,by_unitonduly_task, andcatalog_treeonduly_duty— each reachable from app navigation, plustest/views.test.ts.Gates
All four green on
997782a, the final commit on this branch:pnpm validate✓ Validation passed;--jsonreportswarnings: [],conversions: []pnpm typecheckpnpm testTests 289 passed (289),Test Files 9 passed (9)pnpm build✓ Build completeThe zero-warning validate is the acceptance criterion, not a formality: with the
ganttblock deleted the same command reportsview/layout-without-binding, which is how the reading was confirmed to be a real green rather than an absent check.The views
board— kanban bystatus. Columns, their order and their labels come fromduly_task.status's ownoptions, so the board cannot drift from the object. Dragging a card writes one field: measured against the renderer's move handler,dataSource.update(object, id, { [groupBy]: toColumnId })—statusand nothing else, the same write the row action does. That write is not gated oninlineEdit; it is gated on update permission.inlineEdit: trueis still declared, and it is not inert — the toolbar lets a viewer switch this view to its grid visualisation, and the adapter honours the flag on that branch.schedule— gantt,visible_from→due_date, grouped by owner, week granularity. The span is the lead time. A filter requires both dates to be present, which is a correctness constraint rather than a scope narrowing: the renderer maps a missing date tonew Date(), so an undated one-off would draw a zero-width bar on today and read as load that does not exist.recent— timeline onlast_update_atdescending, coloured bystatus.last_update_atis hook-stamped on a status change or a note edit and deliberately does not advance on an administrative write, so this is "what has been happening", not "what has been touched".by_unit— grid grouped bybusiness_unit, carrying thebulkActionDefsfrom #41 unchanged. Those are the declarative data-plane write and were copied, not restructured; they are on this view because the adapter honoursbulkActionDefson the grid branch only.catalog_tree— duties by business unit, then owner. Shipped astype: 'grid'+ two-levelgrouping, nottype: 'tree'— see the deviation below.Navigation:
boardunder My work;schedule/recent/by_unitunder Team;catalog_treeunder Setup. Every edit appends to an existingchildrenarray so #27 can add its entry beside these without a conflict.board's placement is a product rule, not taste. Dragging a card writesstatus, and managers do not enter status — assigning is their only write. A drag-to-done surface in the manager's section invites exactly the write the model refuses them.test/views.test.tspins the placement so it does not migrate.Status colour
One source of truth:
duly_task.status'soptions[].color. No view carries a colour map, and the test fails if one appears. Every lens that can name a colour source namesstatus.The gantt is the deliberate exception, and it inverts. Measured on
@objectstack/console17.2.0: the gantt renderer putsrecord[colorField]straight intobackgroundColor, socolorField: 'status'setsbackground: "open"— invalid CSS, silently dropped, every bar identical. With the key absent the same renderer falls through to its status-derived palette and the bars separate by state. Declaring the documented key is strictly worse than omitting it. The omission is pinned by a test with the reasoning attached so nobody "fixes" it back, and it is filed upstream as objectstack-ai/objectstack#14110.Deviation:
catalog_treeis a grouped grid, not atreeviewThe platform's
treeview is a self-referencing hierarchy:TreeConfigSchematakes aparentFieldsingle-parent pointer and the renderer nests a record under another record of the same object by matchingrecord[parentField]against sibling ids.duly_dutyhas no self-reference — its lookups point atsys_business_unit,duly_catalog_itemand a user — so atype: 'tree'view here resolves no parent for any row, puts every duty at depth 0 and renders a flat table. Not empty: a complete, correct-looking table that never expands.plugin-treenever readsgrouping, so declaring the two levels alongside it changes nothing, and no gate says so —view/layout-without-bindingdoes not covertreeat all.That is precisely the "renders wrong while authoring reports success" shape this card exists to prevent, so it was not shipped. The grid's grouping hook is the honest expression of the same idea and it is measured, not assumed: it recurses through
grouping.fieldsbuilding nestedsubgroupswith per-level collapse — a real two-level hierarchy. Group keys sort by label, never by bucket size.Filed upstream as objectstack-ai/objectstack#14109 with two suggested resolutions. When one lands, this view can become a real tree.
Platform gaps filed upstream
Six, all measured against this repo on
@objectstack/cli17.2.0 /@objectstack/console17.2.0, each with the mutation confirmed on disk before the reading and the tree restored after:view/layout-without-bindingcovers onlykanban/calendar/gantt.timeline,treeandmaphave identical literal-default fallbacks in the renderer ('created_at','name','location') and no gate. Deleting this PR'stimelineblock leavesvalidatereportingwarnings: [].columns,filter,sort,grouping, nor any binding block — andpnpm build, the publish gate, does not catch it either.gantt.startDateField: 'B1_no_such_field'builds clean and renders a blank chart.viewNamenaming a view that does not exist silently opens the default view, keeping its authored label.treehas no value-grouped mode, and misapplying it renders flat rather than empty — thecatalog_treedeviation above.gantt.colorFieldinverts, as described above. The same key resolves three different ways acrossplugin-timeline(authored option colours),plugin-calendar(stable hash) andplugin-gantt(raw value).finding)rowColor.colorsis optional in the schema but required by the only renderer —rowColor: { field }alone is a parse-clean no-op. This one changed no code here; it is why no view authorsrowColor.The renderer measurements objectstack-ai/objectstack#14074 asked for were posted there rather than filed again.
test/views.test.tsTwo halves, kept apart in the file.
A stopgap for objectstack-ai/objectstack#14106, objectstack-ai/objectstack#14107 and objectstack-ai/objectstack#14108 — binding blocks present and complete for all six binding view types, every field reference on every view surface resolved against the object, every nav
viewNameresolved against the declaredlistViews. Written to be deleted when those land, not maintained — the same shape astest/flow-predicates.test.tsfor objectstack-ai/objectstack#14089.Product pins that outlive the platform gaps: the gantt starting at
visible_from, the timeline ordering bylast_update_atdescending, one colour source for status, the gantt's deliberate absentcolorField, nothing ordered or totalled by a count, no filter reaching for a derived flag, the board's nav placement.Every one was reverse-verified — mutate the source, watch the guard turn red, restore:
timelinebinding blockgantt.startDateField→ nonexistent fieldcolorField: 'status'to the ganttviewNametypogroupingfield → nonexistent fieldrowColormapFiles
src/views/task.view.ts,src/views/duty.view.ts,src/apps/duly.app.ts,test/views.test.ts. No other file touched —objectstack.config.tsuntouched,bulkActionDefsfrom #41 unchanged, no changesets in this repo.Generated by Claude Code