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.18.1: the New panel remembers the last one
Adding six tasks for the same person in the same week meant retyping the owner
and both dates six times. A successful Create now leaves those three values
behind and the next New panel opens with them filled in.
In memory and nowhere else, deliberately: one module-level let, cleared by a
refresh. Not localStorage, not prefs, not the sidecar, because this is a fact
about this tab in this sitting rather than a property of the plan or something to
hand to whoever opens it next.
Name and notes always start empty, since they are what makes an item that item
and carrying them over would offer to create the same row twice. Status,
provenance and phase keep their defaults.
Only a create that produced a row updates it: the assignment sits past the
name-required guard, so Cancel, Escape and a rejected create all leave the
remembered values alone.
Milestones: the panel hides the start field but the input keeps its value, so a
milestone's date is remembered from the same field a task's deadline uses and the
remembered start comes through unchanged. No special case, and it matches what
the panel looks like it is doing.
The owner label says 'from your last new item' only while values are being
carried, because a prefilled field with no explanation is a confusing field.
Verified headless, including the store assertions done against a live store
rather than an empty one: the app is made to write its prefs for real, the page
is reloaded in the same browser profile, and localStorage then holds exactly one
key containing the seven view preferences with no owner and no date in it, the
sidecar has no key for this, and the panel is back to empty defaults. The
55-check sweep and the 52-key fingerprint are unchanged.
6 files changed
+74−9
README.md+2@@ -38,6 +38,8 @@ Interactive Gantt chart viewer for project timelines. Data-driven, dark by defau **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. +**New items remember the last one.** After you create a task or milestone, the next New panel prefills the owner and both dates from it, so adding several items for the same person in the same week is just names. Name and notes always start empty. It lasts the page session only: a refresh clears it, and it is never saved to your preferences or the plan, so it is never handed to anyone else.+ **Undo.** Ctrl+Z undoes date edits, dependency changes, drag operations, row reorders, and table edits to owner, status and notes (100-level stack). **Import.** JSON (native format), CSV, MS Project XML, and Jira CSV. Drag-and-drop or file picker. A file with rows that carry no usable date is rejected whole, naming the rows, rather than having a date invented for it. A row that cannot be dated is left out of the chart with a warning instead of blanking the plan.
SKILL.md+1−1@@ -187,7 +187,7 @@ rather than a category. ## Features -- **New** button: create a task or milestone. Created rows live in the sidecar as `added: [...]`, keyed in a `#new-` namespace that cannot collide with the data file's `#row<n>`, and are merged into every export+- **New** button (or Shift+N): create a task or milestone. After a successful create, the next New panel prefills the owner and both dates from the one you just made, so a run of tasks for the same person in the same week is names and nothing else. Name and notes always start empty; status, provenance and phase keep their defaults. It is in-memory for the page session only: a refresh clears it, and it is never written to localStorage, the preferences or the sidecar, so it is never carried to anyone else. Cancel does not update it, only a create that produced a row. Created rows live in the sidecar as `added: [...]`, keyed in a `#new-` namespace that cannot collide with the data file's `#row<n>`, and are merged into every export - **Text size** A-minus/A+ in the header: seven steps from 85% to 150%, applies to the chart and the table, persists in `localStorage` as `prefs.fs`. Row and bar geometry scale with the type - **Fit** button beside the cell-size picker: picks the finest grain whose whole span fits the pane, so the plan is fully visible with no horizontal scrolling. Chart-only - **Gantt / Table toggle** pinned first in the header, left of every conditional control so its position never shifts. Two lenses over the same items: the chart answers "when", the table answers "whose, and by when". See The table view below
docs/BUILD-NOTES.md+43@@ -3,6 +3,49 @@ Decisions taken while building, and the places where the plan and the code disagreed. Newest first. +## Build notes, 2026-08-10 (0.18.1, the New panel remembers the last one)++Adding six tasks for the same person in the same week meant retyping the owner and+both dates six times. A successful Create now leaves those three values behind, and+the next New panel opens with them filled in.++**In memory, and nowhere else, on purpose.** One module-level `let`, cleared by a+refresh. Not localStorage, not prefs, not the sidecar, because this is a fact about+this tab in this sitting: it is not a property of the plan, it is not something to+hand to whoever opens the plan next, and a value that outlives the sitting stops+being a convenience and becomes a wrong default nobody remembers setting.++**Name and notes always start empty**, because they are what makes an item that+item and carrying them over would be offering to create the same row twice. Status+keeps its not-started default, provenance keeps its own, and phase is untouched.++**Only a create that produced a row updates it.** The assignment sits past the+name-required guard and next to the push into `addedItems`, so Cancel, Escape and a+create rejected for a missing name all leave the remembered values alone. Verified+all three.++**Milestones, stated.** The panel hides the start field for a milestone but the+input keeps its value, so the remembered start comes through a milestone unchanged+while its date lands in the same field a task's deadline uses and is remembered+from there. That needed no special case, and it is what the panel looks like it is+doing: the field you filled in is the field that is remembered.++A prefilled field with no explanation is a confusing field, so the owner label+carries "from your last new item" only while values are actually being carried.++Verified headless: a first open is pristine with today and today plus fourteen and+no hint; creating Caleb / 2026-09-02 / 2026-09-25 and reopening prefills all three+with name and notes empty and status and provenance at their defaults; a second+create with Dana wins; typing Mallory and hitting Cancel leaves Dana; a create+blocked for a missing name leaves Dana and creates nothing; a milestone keeps its+date and owner and leaves the remembered start where it was. Then the store+assertions, done properly rather than against an empty store: the app is made to+write its prefs for real, the page is RELOADED in the same browser profile, and+localStorage then holds exactly one key whose contents are the seven view+preferences with no owner and no date anywhere in it, the sidecar has no key for+this, and the panel is back to empty defaults. The 55-check sweep and the 52-key+fingerprint are unchanged. Zero JS errors.+ ## Build notes, 2026-08-10 (0.18.0, bars snap to the visible grid) Dragged bars used to land wherever the cursor stopped, which put them at
gantt.html+25−5@@ -2808,10 +2808,25 @@ function openItemModal(r,carriedName){ // The same inline editor panel the milestone editor uses, so creating a row and // editing one feel like the same gesture. Everything typed here flows into the // same rendering paths as data-file content, which is where the escaping lives.+// What the last successful Create left behind, so adding six tasks for the same+// person in the same week is six names typed and nothing else. In-memory on+// purpose: a refresh clears it, and it is never written to prefs, localStorage or+// the sidecar, so it is a fact about this tab in this sitting and not about the+// plan or about anyone else reading it. Cancel never touches it; only a create that+// actually produced a row does.+let lastCreated=null; function openCreateEditor(){ closeEditor(); const today=isoDay(new Date()); const plus=addDays(today,14);+ // Name and notes always start empty: they are what makes an item that item, and+ // carrying them over would offer to create the same row twice. Status and+ // provenance keep their own defaults and phase is untouched.+ const memo=lastCreated||{};+ const ownerVal=memo.owner||'';+ const startVal=memo.start||today;+ const endVal=memo.end||plus;+ const carried=lastCreated?'<span class="de-hint">from your last new item</span>':''; editorEl.innerHTML=`<div class="drag-bar"></div> <div class="batch-title">New item</div> <div class="de-row">@@ -2820,12 +2835,12 @@ function openCreateEditor(){ </div> <div class="de-note-row"><label>name <span class="de-hint">required</span></label><input type="text" id="ncName" placeholder="What is it?"></div> <div class="de-row" style="margin-top:8px">- <div style="flex:1"><label>owner</label><input type="text" id="ncOwner" list="ownerList" placeholder="one name"></div>+ <div style="flex:1"><label>owner ${carried}</label><input type="text" id="ncOwner" list="ownerList" placeholder="one name" value="${esc(ownerVal)}"></div> <div style="flex:1"><label>status</label><select id="ncStatus">${STATUS_ORDER.map(x=>`<option value="${x}">${statusLabel[x]}</option>`).join('')}</select></div> </div> <div class="de-row">- <div id="ncStartWrap"><label>start</label><input type="date" id="ncStart" value="${today}"></div>- <div><label id="ncEndLabel">deadline</label><input type="date" id="ncEnd" value="${plus}"></div>+ <div id="ncStartWrap"><label>start</label><input type="date" id="ncStart" value="${startVal}"></div>+ <div><label id="ncEndLabel">deadline</label><input type="date" id="ncEnd" value="${endVal}"></div> </div> <div class="de-note-row"><label>notes</label><input type="text" id="ncDesc" placeholder="Shown on hover and in the table"></div> <div class="de-note-row"><label>provenance <span class="de-hint">where this came from</span></label><input type="text" id="ncProv" value="Added by hand"></div>@@ -2853,8 +2868,8 @@ function openCreateEditor(){ if(!name){const er=$('ncErr');er.textContent='A name is required.';er.style.display='';$('ncName').focus();return;} const isMs=$('ncType').value==='milestone'; const phase=parseInt($('ncPhase').value,10)||0;- const end=$('ncEnd').value||plus;- const start=$('ncStart').value||today;+ const end=$('ncEnd').value||endVal;+ const start=$('ncStart').value||startVal; const r={type:isMs?'milestone':'task',name,phase}; if(isMs){ r.date=end; } else { r.id=freshId(name); r.start=start<=end?start:end; r.end=end; }@@ -2863,6 +2878,11 @@ function openCreateEditor(){ const st=$('ncStatus').value; if(st)r.status=st; const desc=$('ncDesc').value.trim(); if(desc)r.description=desc; const prov=$('ncProv').value.trim(); if(prov)r.provenance=prov;+ // Only now, past validation and with a row actually being made. A milestone+ // hides the start field but the input keeps its value, so `start` is whatever+ // was carried in and the remembered start comes through a milestone unchanged,+ // which is what the panel looks like it is doing.+ lastCreated={owner:owner||'', start, end}; addedItems.push(r); insertRow(r); undoStack.push({type:'create',r});
package.json+1−1@@ -2,7 +2,7 @@ "slug": "adom-gantt", "title": "Adom Gantt", "type": "app",- "version": "0.18.0",+ "version": "0.18.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.18.0",+ "version": "0.18.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]" }-}+}