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.31.1: pinned header no longer covers milestone caps/today tag; top band clears the sticky overlays
4 files changed
+73−11
docs/BUILD-NOTES.md+30@@ -3,6 +3,36 @@ Decisions taken while building, and the places where the plan and the code disagreed. Newest first. +## Build notes, 2026-08-17 (0.31.1, nothing hides under a pinned band)++0.31.0's sticky section header was opaque and z-above the content, and the+line-style milestone caps all live in a band at the TOP of the timeline. The+first section's header pins in exactly that band, so at rest it covered the caps+sitting under it: milestone tags ("Full dry run done...", "Capitol Hill+reception") were clipped at their tops. Measured: 3 caps hidden under the pinned+header at scroll 0.++**Two changes, both about the top band clearing the pinned overlays.** First, the+caps band and the today tag now START below the pinned header, offset by the+header's height (read from a real group row, so it tracks the font scale). At rest+they sit fully below the pinned axis+header instead of under it. Second, a cap or+the today tag that scrolls UP into the pinned band is hidden the instant its top+crosses the band's bottom edge, and reappears whole once it is fully below again.+That is the clean-edge version of "content scrolls up and vanishes under the+pinned header": a tall two-line tag never shows a clipped sliver, because it is+either wholly below the band or gone, never straddling it.++The today line, the dashed milestone rules and the dep arrows still pass beneath+the pinned overlays, which is fine; only TEXT (tags, the today pill) is masked so+it is never cut.++Proven with a rect-clearance sweep at six scroll depths: zero milestone tags and+zero today tags straddle the pinned axis or header at any of them, the first task+row and the first cap sit fully below the header at rest, all ten caps are visible+at rest and reappear after a scroll round trip, and it holds under a two-step font+scale, a theme switch and the diamond style. Thirteen suites green including the+per-cap drag and the sticky-swap sweep, zero JS errors.+ ## Build notes, 2026-08-17 (0.31.0, the axis and the section header stay put) Two things now stay pinned while the plan scrolls: the date axis at the top,
gantt.html+40−8@@ -278,7 +278,7 @@ body.col-resizing { cursor: col-resize; user-select: none; } over every bar it crosses and eat their clicks, which is a worse trade than a tooltip. The tag is the marker's affordance, so it carries the tooltip, with a little more padding to make it a comfortable target. */-.today-tag { position: absolute; top: 4px; transform: translateX(-50%); font-family: 'JetBrains Mono', monospace; font-size: 9px; color: var(--today); background: var(--bg); padding: 2px 6px; border-radius: 3px; border: 1px solid var(--border); pointer-events: auto; cursor: default; z-index: 9; white-space: nowrap; }+.today-tag { position: absolute; top: calc(var(--group-h) + 4px); transform: translateX(-50%); font-family: 'JetBrains Mono', monospace; font-size: 9px; color: var(--today); background: var(--bg); padding: 2px 6px; border-radius: 3px; border: 1px solid var(--border); pointer-events: auto; cursor: default; z-index: 9; white-space: nowrap; } .today-tag:hover { border-color: var(--today); } .dep-svg { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 4; overflow: visible; } .dep-svg path { fill: none; stroke: var(--accent); stroke-width: 1.5; opacity: 0.5; transition: opacity 0.15s; }@@ -2552,11 +2552,31 @@ function updatePinnedHeader(){ const groups=items.filter(isDerivedGroup); let active=null; groups.forEach(g=>{ if(g._lDiv && (g._lDiv.offsetTop-top)<=axis+0.5) active=g; });- if(active===pinnedGroup)return;- groups.forEach(g=>{ const on=g===active;- if(g._lDiv)g._lDiv.classList.toggle('is-pinned',on);- if(g._bDiv)g._bDiv.classList.toggle('is-pinned',on); });- pinnedGroup=active;+ if(active!==pinnedGroup){+ groups.forEach(g=>{ const on=g===active;+ if(g._lDiv)g._lDiv.classList.toggle('is-pinned',on);+ if(g._bDiv)g._bDiv.classList.toggle('is-pinned',on); });+ pinnedGroup=active;+ }+ maskTopBand(active);+}+// The milestone caps and the today tag live in a band at the top of the timeline.+// A cap that has scrolled up INTO the pinned axis+header band would otherwise show+// a clipped sliver of its (sometimes two-line) label under the overlay. Hide any+// whose top has crossed the pinned band's bottom edge; they reappear, whole, the+// moment they are fully below it again. This is the clean-edge version of "content+// scrolls up and disappears under the pinned header".+function maskTopBand(active){+ const boundary=(active&&active._bDiv)?active._bDiv.getBoundingClientRect().bottom+ :timelineEl.getBoundingClientRect().top+46*(prefs.fs||1);+ if(prefs.msStyle==='line'){+ items.forEach(r=>{ if(r.type!=='milestone'||!r._msLine)return;+ const cap=r._msLine.querySelector('.ms-cap'); if(!cap)return;+ cap.style.visibility=(cap.getBoundingClientRect().top>=boundary-1)?'':'hidden'; });+ }+ if(todayTagEl&&todayTagEl.style.display!=='none'){+ todayTagEl.style.visibility=(todayTagEl.getBoundingClientRect().top>=boundary-1)?'':'hidden';+ } } function wireChartWheel(){ const wrap=timelineEl.closest('.chart-wrap')||timelineEl.parentElement;@@ -2625,12 +2645,24 @@ function relayout(){ // Wrapping narrows a long milestone title but cannot stop two adjacent ones from // colliding, so caps that still overlap drop to the next row. Measured left-to-right // in date order, each cap takes the first row it fits on.+// The pinned section header, opaque and sticky since 0.31.0, sits in the same top+// band the milestone caps live in. So the caps band starts BELOW it: at rest, a+// cap under the first section's header is no longer hidden by it, and a cap that+// scrolls up still slides under the header (it is z-below) rather than being+// clipped in place. `base` is the header height plus a hair, read from a real+// group row so it tracks the font scale.+function capBandBase(){+ const g=items.find(isDerivedGroup);+ const h=(g&&g._bDiv&&g._bDiv.offsetHeight)|| (28*(prefs.fs||1));+ return Math.round(h)+4;+} function layoutMsCaps(){ if(prefs.msStyle!=='line')return;+ const base=capBandBase(); const caps=[]; items.filter(r=>r.type==='milestone'&&r._msLine&&!r._collapsed).forEach(r=>{ const cap=r._msLine.querySelector('.ms-cap');- if(cap){cap.style.top='2px';caps.push({cap,x:toX(r.date)});}+ if(cap){cap.style.top=base+'px';caps.push({cap,x:toX(r.date)});} }); caps.sort((a,b)=>a.x-b.x); const GAP=6;@@ -2658,7 +2690,7 @@ function layoutMsCaps(){ n++; } rows[n].push([l,r]);- c.cap.style.top=(2+n*rowH)+'px';+ c.cap.style.top=(base+n*rowH)+'px'; }); }
package.json+1−1@@ -2,7 +2,7 @@ "slug": "adom-gantt", "title": "Adom Gantt", "type": "app",- "version": "0.31.0",+ "version": "0.31.1", "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.31.0",+ "version": "0.31.1", "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]" }-}+}