app
Adom Gantt
Public Made by Adomby adom
Interactive Gantt chart viewer for project timelines, with a table view and an analytics page over the same data. Data-driven, dark by default with a print-friendly light mode, and fully editable in the browser: drag bars to move or resize, multi-select and batch-shift dates, manage dependency arrows inline, reorder rows, filter by phase, and undo. One click auto-schedules the whole plan so every dependency lines up finish-to-start on working days, skipping weekends and US federal holidays, push
← Commit history
0.30.2: bar fill is a pure function of the row's current phase
4 files changed
+60−4
docs/BUILD-NOTES.md+38@@ -3,6 +3,44 @@ Decisions taken while building, and the places where the plan and the code disagreed. Newest first. +## Build notes, 2026-08-17 (0.30.2, a bar cannot disagree with its lane)++The Drone Project lane showed mixed bar colours, some teal, some blue. Not a+rendering glitch: the bars were telling the truth about a stale value.++**Root cause, traced not guessed.** Sixteen Drone bars had inline+`background: var(--ph1)` while the lane's colour is `var(--ph3)`, and every one of+those tasks had `phase: 5` in the served data, so the phase was RIGHT and the+colour was wrong. Those tasks have a data-file phase of 0 (Workcells, the only lane+that existed when they were made) and a sidecar override of 5 (Drone, where they+were moved). At load `buildRows` paints each bar with `phaseColor(0)` = the ph1+slot; then `applyState` sets `r.phase = 5` and re-homes the row but never touches+the inline colour. Bar fill was set imperatively at build and patched at a handful+of phase-change sites, so a path that moved a phase without touching the style,+`applyState` here and `deletePhase`'s reassign among them, left the old lane's+colour behind. It survived reload because the same sequence repeats every load.++**Fix: bar fill is a pure function of the row's CURRENT phase.** `recolorByPhase`+sets every bar and milestone rule from `phaseColor(r.phase)`, and it runs from the+two choke points every change flows through, `reflowRows` (drag, phase edit,+Alt+arrow, delete-reassign, and the load-time `applyOrder`) and `updateBar`. So a+bar can never disagree with its lane, and the existing mismatch heals on the next+relayout, which every reload performs: measured at 16 mismatched Drone bars before,+0 after a plain reload with nothing else done.++**Both colour kinds, honoured.** `phaseColor` returns an explicit `p.color`+unchanged, which is theme-invariant by the 0.30.0 model, or the `var(--phN)` ramp+slot, which stays a CSS variable in the inline style and so is recoloured by a+theme switch for free. A task moved into a lane takes that lane's EFFECTIVE colour+whichever kind it is: verified a ramp lane imparts its ramp slot, an explicitly+orange lane imparts `#f0883e` to both the rows already in it and a row dragged in,+and a dark-to-light switch moves a ramp bar from rgb(0,184,177) to rgb(0,164,157)+while the orange bar stays put.++Every phase-change path recolours: table phase dropdown, gantt vertical drag,+Alt+arrow cross-lane, and the section editor's delete-with-reassign. Eleven suites+green including the sections and unification sweeps, zero JS errors.+ ## Build notes, 2026-08-17 (0.30.1, the last two data events read like sentences) `plan.unify` and `plan.freight` were both landing in the changelog as
gantt.html+19−1@@ -1327,8 +1327,26 @@ function orderKeyMap(){ function reflowRows(){ items.forEach((r,i)=>{r._itemIdx=i;labelsEl.appendChild(r._lDiv);bars.appendChild(r._bDiv);if(r.id&&barEls[r.id])barEls[r.id].idx=i;}); items.filter(r=>r.type==='milestone'&&r._msLine).forEach(r=>bars.appendChild(r._msLine));+ recolorByPhase(); keepTailLast(); }+// Bar fill and milestone-rule colour are a PURE FUNCTION of the row's current+// phase, nothing else. Before this they were set imperatively at build time and+// patched at a handful of phase-change sites, so any path that changed a phase+// without also touching the inline style left the bar showing its old lane's+// colour: a task with a data-file phase of 0 and a sidecar override of 5 was built+// teal-slot and never recoloured, which is the mixed-colour Drone lane. phaseColor+// returns an explicit p.color unchanged (theme-invariant by design) or the+// var(--phN) ramp slot (which the theme recolours for free), so both kinds land+// on the destination lane's effective colour.+function recolorByPhase(){+ items.forEach(r=>{+ if(isDerivedGroup&&isDerivedGroup(r))return;+ const c=phaseColor(r.phase);+ if(r.id&&barEls[r.id])barEls[r.id].bar.style.background=c;+ if(r._msLine)r._msLine.style.color=c;+ });+} function refreshChartGeometry(){ if(prefs.view==='table')return; layoutMsCaps(); sizeTailroom(); drawDeps();@@ -3448,7 +3466,7 @@ function refreshMsChrome(r){ // restating those too. This used to patch only the geometry and the dates, so a // description restored from the sidecar (or edited in the table) left the bar // hovering the version that was in the data file.-function updateBar(id){const r=items.find(d=>d.id===id),be=barEls[id];if(!r||!be)return;if(r._noteEl)r._noteEl.textContent=r.note||'';const x1=toX(r.start),x2=toX(r.end);be.bar.style.left=x1+'px';be.bar.style.width=Math.max(x2-x1,6)+'px';be.bar.dataset.dates=fmt(r.start)+' → '+fmt(r.end);be.bar.dataset.title=r.name;be.bar.dataset.body=r.description||'';be.bar.dataset.prov=provLabel(r);drawDeps();}+function updateBar(id){const r=items.find(d=>d.id===id),be=barEls[id];if(!r||!be)return;if(r._noteEl)r._noteEl.textContent=r.note||'';const x1=toX(r.start),x2=toX(r.end);be.bar.style.left=x1+'px';be.bar.style.width=Math.max(x2-x1,6)+'px';be.bar.style.background=phaseColor(r.phase);be.bar.dataset.dates=fmt(r.start)+' → '+fmt(r.end);be.bar.dataset.title=r.name;be.bar.dataset.body=r.description||'';be.bar.dataset.prov=provLabel(r);drawDeps();} function closeEditor(){editorEl.classList.remove('show');editorEl.innerHTML='';} function closeMenus(){exportMenu.classList.remove('show');importMenu.classList.remove('show');}
package.json+1−1@@ -2,7 +2,7 @@ "slug": "adom-gantt", "title": "Adom Gantt", "type": "app",- "version": "0.30.1",+ "version": "0.30.2", "description": "Interactive Gantt chart viewer for project timelines, with a table view over the same data. Data-driven, dark by default with a print-friendly light mode, and fully editable in the browser: drag bars to move or resize, multi-select and batch-shift dates, manage dependency arrows inline, reorder rows, filter by phase, and undo. Toggle to the table for every task and milestone as a row with owner, deadline and status, filtered per owner and edited in place. Every edit is recorded in an append-only changelog so an AI can read what a human changed and build on it. Imports JSON, CSV, MS Project XML and Jira CSV; exports JSON, CSV, SVG and a self-contained HTML snapshot.", "main": "server.js", "bin": {
page.json+2−2@@ -2,7 +2,7 @@ "slug": "adom-gantt", "title": "Adom Gantt", "type": "app",- "version": "0.30.1",+ "version": "0.30.2", "description": "Interactive Gantt chart viewer for project timelines, with a table view over the same data. Data-driven, dark by default with a print-friendly light mode, and fully editable in the browser: drag bars to move or resize, multi-select and batch-shift dates, manage dependency arrows inline, reorder rows, filter by phase, and undo. Toggle to the table for every task and milestone as a row with owner, deadline and status, filtered per owner and edited in place. Every edit is recorded in an append-only changelog so an AI can read what a human changed and build on it. Imports JSON, CSV, MS Project XML and Jira CSV; exports JSON, CSV, SVG and a self-contained HTML snapshot.", "main": "server.js", "bin": {@@ -63,4 +63,4 @@ "name": "Drew Owens", "email": "[email protected]" }-}+}