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
Publish 0.6.0
5 files changed
+52−22
README.md+3−5@@ -14,7 +14,7 @@ Interactive Gantt chart viewer for project timelines. Data-driven, dark by defau **Dependency arrows.** Teal bezier curves with arrowheads show task relationships. Click any bar to see incoming ("needs") and outgoing ("blocks") dependencies. Add or remove dependencies inline. -**Legend filtering.** Click any phase, "Milestone", or "Dependencies" in the legend bar to toggle visibility. Dimmed items fade to 15% opacity.+**Legend filtering.** Click any phase, "Milestone", "Dependencies" or "Today" in the legend bar to toggle visibility. Dimmed items fade to 15% opacity; hiding "Today" removes the marker line. **Undo.** Ctrl+Z undoes date edits, dependency changes, drag operations, and row reorders (100-level stack). @@ -24,9 +24,7 @@ Interactive Gantt chart viewer for project timelines. Data-driven, dark by defau **Auto-save.** Edits persist to a sidecar JSON file next to the data file. Delete the sidecar to reset. -**Fills the window.** Months stretch to the width of the pane, and stop at 34px so a long plan scrolls horizontally rather than squashing into unreadability. Resizing reflows without a reload.--**Milestone style.** Diamonds, or dashed vertical rules spanning the full chart height, colored by phase. Toggle in the header.+**Fills the window.** Cells stretch to the width of the pane, and stop at each grain's minimum so a long plan scrolls horizontally rather than squashing into unreadability. Resizing reflows without a reload. **Light mode.** Dark by default. The Light toggle switches to a print-friendly theme with deepened bar colors, and printing always uses it whatever the screen is set to. @@ -36,7 +34,7 @@ Interactive Gantt chart viewer for project timelines. Data-driven, dark by defau **Cell size.** Days, Weeks, Months, Quarters or Years. Each grain carries its own minimum cell width, sized to the widest label it draws, so a fine grain scrolls rather than squashing and a coarse one stretches to fill. Days adds weekend shading and a full calendar picker. -**Editable milestones.** Drag a milestone sideways to move its date, or click it to edit its name, date and description. Line style is the default: the tag on the dashed rule is the handle and the milestone's own row is hidden, since the rule already spans the chart. Diamond style keeps the row.+**Editable milestones.** Drag a milestone sideways to move its date, or click it to edit its name, date and description. Line style is the default: the tag on the dashed rule is the handle and the milestone's own row is hidden, since the rule already spans the chart. Long titles wrap, and a tag that would still collide with its neighbour drops to a second row. Diamond style keeps the row. **Bar notes.** Put a short label on the bar body, the way durations are annotated on a printed plan. Double-click a bar to type one, or use the note field in the editor.
SKILL.md+9−11@@ -132,7 +132,7 @@ recommended default: the ramp is theme-aware, so the same plan stays on-brand in dark mode and stays legible when printed. Setting an explicit `color` opts that phase out of theming, and it will not adapt for paper. -The ramp is teal, purple, blue, then a second step of each:+The ramp cycles teal, purple, blue: | Phase | Dark (default) | Light / print | |-------|----------------|---------------|@@ -142,9 +142,8 @@ The ramp is teal, purple, blue, then a second step of each: The ramp cycles the three brand hues rather than inventing lighter or darker fourths. On a dark ground a second teal has to be lighter to be told apart, and a pale teal-reads as off-brand. Sections are separated by the neutral grey banding and the group-header instead, so a repeat three rows down is not confusing. Adjacent phases are-always different.+reads as off-brand. Sections are separated by the group header instead, so a repeat three rows down is+not confusing. Adjacent phases are always different. There is no background band behind a phase. The bar colour and the group header row carry the section on their own, which keeps the chart body clean and means colour on@@ -156,25 +155,24 @@ rather than a category. ## Features -- **Click legend** to filter phases, milestones, or dependency arrows+- **Click legend** to filter phases, milestones, dependency arrows, or the today line - **Click a bar** to open the date editor with start/end pickers and dependency management - **Drag bar edges** to resize (changes start or end date)-- **Ctrl+Z** to undo (50-level stack)+- **Ctrl+Z** to undo (100-level stack) - **Auto-save** to `<datafile>-state.json` sidecar (800ms debounce) - **Auto-load** saved state on page refresh - **Dynamic date range** computed from the data every render - **Project start** in the header. Changing it moves the WHOLE plan: every task, milestone and phase shifts by the same number of days, so durations and gaps are preserved. Revert puts it back to the data file's own start. This is a real edit, recorded and undoable, not a view setting - **Cell size** picker: Days, Weeks, Months, Quarters, Years. Each grain has its own minimum cell width sized to the widest label it draws, so a fine grain scrolls rather than squashing while a coarse one stretches to fill the pane. Day and week positions are exact; month, quarter and year approximate within a month - **Bar notes** put a short label on the bar body. Double-click a bar to type one, or use the note field in the editor-- **Milestones are editable too**: drag one sideways to move its date, or click it for a panel with its name, date and description. Line style is the default: the tag on the dashed rule is the handle, and the milestone's grid row is hidden since the rule already carries it. Diamond style keeps the row+- **Milestones are editable too**: drag one sideways to move its date, or click it for a panel with its name, date and description. Line style is the default: the tag on the dashed rule is the handle, and the milestone's grid row is hidden since the rule already carries it. Diamond style keeps the row. Long titles wrap, and any tag that would still collide with its neighbour drops to a second row, so tags never overlap - **Dynamic today marker** shows current date-- **Fills the window**: months stretch to the pane width, and stop at 34px so a long plan scrolls rather than squashing-- **Milestone style toggle**: diamonds, or dashed vertical rules spanning the chart - **Light mode** for printing (dark is the default; printing always uses the light theme) - **Scroll past the last row** so the bottom of the plan can sit centred in the pane -View preferences (theme, milestone style, start-date override) persist in `localStorage`-per browser. They are a view concern, so they are not written to the data file.+View preferences (theme, milestone style, cell size, today marker) persist in+`localStorage` per browser. They are a view concern, so they are not written to the+data file. The project start is NOT a view preference: it edits real dates. ## Read the changelog before you edit a plan
gantt.html+38−4@@ -171,7 +171,7 @@ header .act-btn svg { width: 14px; height: 14px; } /* Line style: a dashed vertical rule spanning the whole chart at the milestone date. */ .ms-line { position: absolute; top: 0; bottom: 0; width: 0; border-left: 1.5px dashed currentColor; opacity: 0.75; z-index: 4; pointer-events: none; } .ms-line .ms-cap:hover { background: var(--elevated); filter: brightness(1.25); }-.ms-line .ms-cap { position: absolute; top: 2px; left: 0; transform: translateX(-50%); pointer-events: auto; cursor: pointer; font-family: 'JetBrains Mono', monospace; font-size: 9px; line-height: 1; color: inherit; background: var(--bg); padding: 1px 4px; border-radius: 3px; border: 1px solid currentColor; white-space: nowrap; }+.ms-line .ms-cap { position: absolute; top: 2px; left: 0; transform: translateX(-50%); pointer-events: auto; cursor: pointer; font-family: 'JetBrains Mono', monospace; font-size: 9px; line-height: 1.25; color: inherit; background: var(--bg); padding: 2px 4px; border-radius: 3px; border: 1px solid currentColor; white-space: normal; overflow-wrap: anywhere; text-align: center; max-width: 132px; width: max-content; } :root[data-ms="diamond"] .ms-line { display: none; } :root[data-ms="line"] .ms-diamond { display: none; } /* In line style the dashed rule and its tag carry the milestone, so the grid row is@@ -306,7 +306,7 @@ document.title = `${project.title} — adom-gantt`; // ── View preferences (theme, milestone style, start-date override) ── const PREF_KEY = 'adom-gantt:prefs'; const prefs = Object.assign(- { theme: 'dark', msStyle: 'line', granularity: 'month' },+ { theme: 'dark', msStyle: 'line', granularity: 'month', showToday: true }, (() => { try { return JSON.parse(localStorage.getItem(PREF_KEY)) || {}; } catch { return {}; } })() ); function savePrefs(){ try { localStorage.setItem(PREF_KEY, JSON.stringify(prefs)); } catch {} }@@ -608,7 +608,16 @@ phases.forEach((ph,i)=>{const el=document.createElement('div');el.className='leg const msL=document.createElement('div');msL.className='legend-item';msL.innerHTML='<div class="legend-diamond"></div>Milestone';msL.addEventListener('click',()=>{msVis=!msVis;msL.classList.toggle('off',!msVis);applyFilter();});legendEl.appendChild(msL); const depL=document.createElement('div');depL.className='legend-item';depL.innerHTML='<svg width="14" height="10" style="flex-shrink:0"><path d="M0,5C4,5 10,5 14,5" stroke="var(--accent)" stroke-width="1.5" fill="none" opacity="0.6"/><polygon points="14,5 10,3 10,7" fill="var(--accent)" opacity="0.6"/></svg>Dependencies'; depL.addEventListener('click',()=>{depsVis=!depsVis;depL.classList.toggle('off',!depsVis);depSvg.classList.toggle('hidden',!depsVis);});legendEl.appendChild(depL);-legendEl.appendChild(Object.assign(document.createElement('div'),{className:'legend-item static',innerHTML:'<div class="legend-line"></div>Today'}));+// Today toggles from the legend like Milestone and Dependencies, rather than adding+// separate chrome: the legend is already where this app hides and shows chart elements.+const todayL=document.createElement('div');todayL.className='legend-item';+todayL.innerHTML='<div class="legend-line"></div>Today';+todayL.addEventListener('click',()=>{+ prefs.showToday=!prefs.showToday;savePrefs();+ todayL.classList.toggle('off',!prefs.showToday);+ positionToday();+});+legendEl.appendChild(todayL); // ── Build chart ── fitCOL();@@ -708,11 +717,12 @@ items.forEach((r,idx)=>{ // Appended after the rows so the dashed rules sit above the phase bands. msLineEls.forEach(ln=>bars.appendChild(ln)); document.documentElement.setAttribute('data-ms', prefs.msStyle);+if(prefs.showToday===false)todayL.classList.add('off'); const today=new Date(); function todayX(){return mDiff(START,today)*COL+(today.getDate()/30)*COL;} function positionToday(){- const tx=todayX(), on=tx>0&&tx<W;+ const tx=todayX(), on=prefs.showToday!==false && tx>0 && tx<W; if(todayLineEl){todayLineEl.style.left=tx+'px';todayLineEl.style.display=on?'':'none';} if(todayTagEl){todayTagEl.style.left=tx+'px';todayTagEl.style.display=on?'':'none';} }@@ -743,6 +753,7 @@ function relayout(){ else if(r.id&&barEls[r.id]){const b=barEls[r.id].bar,x1=toX(r.start),x2=toX(r.end);b.style.left=x1+'px';b.style.width=Math.max(x2-x1,6)+'px';} }); positionToday();+ layoutMsCaps(); sizeTailroom(); drawDeps(); }@@ -755,6 +766,29 @@ function relayout(){ // Re-appending the rows (a reorder, or applyOrder on load) moves them AFTER the // label column's tail spacer, which then pushes every label down and detaches the // labels from their bars. Put the spacer back at the end whenever rows move.+// 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.+function layoutMsCaps(){+ if(prefs.msStyle!=='line')return;+ const caps=[];+ items.filter(r=>r.type==='milestone'&&r._msLine).forEach(r=>{+ const cap=r._msLine.querySelector('.ms-cap');+ if(cap){cap.style.top='2px';caps.push({cap,x:toX(r.date)});}+ });+ caps.sort((a,b)=>a.x-b.x);+ const rows=[]; // right edge occupied per row+ const GAP=6;+ caps.forEach(c=>{+ const w=c.cap.offsetWidth||80;+ const left=c.x-w/2, right=c.x+w/2;+ let row=0;+ while(rows[row]!==undefined && left < rows[row]+GAP) row++;+ rows[row]=right;+ c.cap.style.top=(2+row*(c.cap.offsetHeight+3))+'px';+ });+}+ function keepTailLast(){ if(typeof labelTail!=='undefined'&&labelTail&&labelTail.parentNode) labelsEl.appendChild(labelTail); if(typeof barTail!=='undefined'&&barTail&&barTail.parentNode) timelineEl.appendChild(barTail);
package.json+1−1@@ -2,7 +2,7 @@ "slug": "adom-gantt", "title": "Adom Gantt", "type": "app",- "version": "0.5.2",+ "version": "0.6.0", "description": "Interactive Gantt chart viewer for project timelines. 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. 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+1−1@@ -2,7 +2,7 @@ "slug": "adom-gantt", "title": "Adom Gantt", "type": "app",- "version": "0.5.2",+ "version": "0.6.0", "description": "Interactive Gantt chart viewer for project timelines. 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. 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": {