← Commit history

Publish 0.64.1

Drew Owens ·e36230e472 · ·parent e15d32d
6 files changed +80−4
SKILL.md+3−1
@@ -360,7 +360,9 @@ with the other two.  **The hour rule (0.63.0).** A task's hours are its **Effort**: the work it takes, in hours, entered in the Table's Effort column or the task editor (`2d` is two 8-hour days,-`1w` a 40-hour week). A task with no Effort is **not estimated** and adds nothing to any+`1w` a 40-hour week; to estimate many at once, select them and use the batch panel's+effort row, which sets the value on every selected task, or clears it with the box+empty, as one undo, since 0.64.1). A task with no Effort is **not estimated** and adds nothing to any total; every hours figure says how many tasks it is missing. The bar is NOT the effort: it is the window the work happens in, and through 0.62 Analytics counted every bar as one person full time for each working day of it, which put a real plan of 154 eight-week
docs/BUILD-NOTES.md+16−1
@@ -3,11 +3,26 @@ Decisions taken while building, and the places where the plan and the code disagreed. Newest first. +## Build notes, 2026-10-05 (0.64.1, effort for a whole selection)++The batch panel gains an effort row under its tags: type an estimate and press Enter or+"Set on all", and every selected TASK gets it (milestones carry none and are left alone);+with the box empty the same press clears the estimates. Like the tag row it acts at once+rather than on Apply, as one undo entry and one `effort` changelog line per row via+`batch-editor`. A line under the box says where the selection stands ("1 of 2 tasks+estimated, 16h in all"), and the box opens holding the value when every selected task+already shares one. Asked for so a 154-task plan can be estimated a lane at a time.++Built as 0.63.1 on 0.63.0 and rebased onto 0.64.0, which another session had published in+the meantime (the registry refused 0.63.1 as older than its latest). The 0.64.0 entry+below named the reference plan by its product name, which John asked public pages not to+do (issue #4), and now says "the reference launch plan".+ ## Build notes, 2026-10-02 (0.64.0, Owner and Overdue in the legend)  Drew: "add filter by owner for gantt page. Let's also add an Overdue tag/filter that is auto added on refresh (maybe also a manual Scan for Overdue button?) to tasks that should be-complete per dates but are not marked complete yet." Built for the instaPCB launch plan:+complete per dates but are not marked complete yet." Built for the reference launch plan: 273 rows, 146 with no owner, 13 named owners.  - **Owner is one choice for both lenses.** The table already filtered by owner, from its own
gantt.html+36
@@ -7848,6 +7848,7 @@ function openBatchEditor(){       <div class="batch-row"><label>shift end</label><input type="number" id="bShiftE" value="0" step="1"> <span>days</span></div>     </div>     <div class="batch-tags" id="bTagsWrap"></div>+    <div class="batch-tags" id="bEffWrap"></div>     <div class="de-actions"><button class="de-btn primary" id="bApply">Apply</button><button class="de-btn secondary" id="bClose">Cancel</button><button class="de-btn danger" id="bDelete" title="${esc(batchDeleteTitle(rows.length,others))}">Delete</button></div>`;   showEditor(window.innerWidth/2-155,window.innerHeight/2-130,'compact'); @@ -7867,6 +7868,41 @@ function openBatchEditor(){   $('bDelete').addEventListener('click',()=>deleteBatchFromPanel());   wireBatchCopy();   renderBatchTags();+  renderBatchEffort();+}+// Effort across the selection (0.63.1). Like the tags, it acts at once rather than on+// Apply: one press sets (or, with the box empty, clears) the estimate on every selected+// TASK, as one undo entry and one changelog batch (`effort`, via batch-editor). Milestones+// carry no effort and are left alone. The line above the box says where the selection+// stands, so estimating a lane is: select it, type 4, Enter.+function renderBatchEffort(){+  const w=$('bEffWrap'); if(!w)return;+  const tasks=selectedRows().filter(r=>r&&!isGroup(r)&&!isMs(r)&&r.id);+  if(!tasks.length){ w.innerHTML=''; return; }+  const est=tasks.filter(r=>effortOf(r)!==null), sum=est.reduce((a,r)=>a+effortOf(r),0);+  const same=est.length===tasks.length&&est.every(r=>effortOf(r)===effortOf(est[0]));+  w.innerHTML=`<div class="batch-row bt-row"><label>effort</label><input type="text" id="bEffort" inputmode="decimal" autocomplete="off" placeholder="hours, 2d, 1w" title="${esc(EFFORT_HELP)}" aria-label="Effort to set on every selected task" value="${same?esc(String(effortOf(est[0]))):''}">`+    +`<button type="button" class="de-btn secondary" id="bEffortSet" title="Set this effort on all ${plural(tasks.length,'selected task')}, or with the box empty, clear it. Milestones carry none. One Ctrl+Z takes it back.">Set on all</button></div>`+    +`<div class="bt-none" id="bEffSum">${est.length} of ${plural(tasks.length,'task')} estimated${est.length?', '+esc(fmtEffort(sum))+' in all':''}</div>`+    +`<div class="bt-none" id="bEffErr" style="color:#f85149;display:none"></div>`;+  const inp=$('bEffort');+  const go=()=>{+    const v=parseEffort(inp.value);+    if(Number.isNaN(v)){ const er=$('bEffErr'); er.textContent='Effort is hours, like 16, 2d or 1w; empty clears it.'; er.style.display=''; inp.focus(); return; }+    const todo=tasks.filter(r=>effortOf(r)!==v);+    if(!todo.length){ inp.focus(); return; }+    snapshotMany(todo.map(r=>r.id));+    todo.forEach(r=>setEffort(r,v));+    scheduleSave('batch-editor');+    if(prefs.view==='table')renderTable();+    renderBatchEffort();+    const i2=$('bEffort'); if(i2)i2.focus();+    flash(saveInd,(v===null?'effort cleared on ':'effort set on ')+plural(todo.length,'task'));+  };+  // Stops its own click: the repaint takes the button out of the page, and a detached+  // target reads as a click outside the panel (the same reason as the tag block above).+  $('bEffortSet').addEventListener('click',e=>{ e.stopPropagation(); go(); });+  inp.addEventListener('keydown',e=>{ e.stopPropagation(); if(e.key==='Enter'){ e.preventDefault(); go(); } else if(e.key==='Escape'){ e.preventDefault(); if(inp.value)inp.value=''; else inp.blur(); } }); } // Tags across the selection (0.60.0). Unlike the dates above, a tag means the same thing // on a milestone as on a task, so these act on EVERY selected row, milestones included,
package.json+1−1
@@ -2,7 +2,7 @@   "slug": "adom-gantt",   "title": "Adom Gantt",   "type": "app",-  "version": "0.64.0",+  "version": "0.64.1",   "description": "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, pushing work LATER only so a lead-time gap you set on purpose is never closed, and one more lights up the critical path, meaning the longest run of work inside every section at once. Cancelled work is left out of both: it never moves, it constrains nothing and it can never glow. Dependency arrows are coloured by the phase of the task they come out of, so where a chain starts reads at a glance. Toggle to the table for every task and milestone as a row with owner, deadline and status, filtered per owner and edited in place, or to analytics for total, remaining and average hours, one row per person carrying both their hours and their status counts, per-section totals, milestones, dependency stats and what is overdue. Copy and paste rows: Ctrl+C the selected tasks and milestones, Ctrl+V to drop clones directly below a selected row or at the end of the lane you are looking at, with dependencies AMONG the copied rows remapped onto the copies so a duplicated chain is still a chain, links pointing outside it left naming the originals, and the whole paste one undo and one changelog line. Five statuses in lifecycle order, not started, in progress, complete, blocked, cancelled, with cancelled keeping the row and its history on the plan while taking its hours out of every total. 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.64.0",+  "version": "0.64.1",   "description": "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, pushing work LATER only so a lead-time gap you set on purpose is never closed, and one more lights up the critical path, meaning the longest run of work inside every section at once. Cancelled work is left out of both: it never moves, it constrains nothing and it can never glow. Dependency arrows are coloured by the phase of the task they come out of, so where a chain starts reads at a glance. Toggle to the table for every task and milestone as a row with owner, deadline and status, filtered per owner and edited in place, or to analytics for total, remaining and average hours, one row per person carrying both their hours and their status counts, per-section totals, milestones, dependency stats and what is overdue. Copy and paste rows: Ctrl+C the selected tasks and milestones, Ctrl+V to drop clones directly below a selected row or at the end of the lane you are looking at, with dependencies AMONG the copied rows remapped onto the copies so a duplicated chain is still a chain, links pointing outside it left naming the originals, and the whole paste one undo and one changelog line. Five statuses in lifecycle order, not started, in progress, complete, blocked, cancelled, with cancelled keeping the row and its history on the plan while taking its hours out of every total. 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": {
test/ui_test.mjs+23
@@ -1121,6 +1121,8 @@ const MANIFEST = [   { sel: '.tb-x', label: 'tag box: remove a chip', terminal: true, by: 'row tags', key: c => c.ds.tb, pin: ['im'] },   { sel: '#bTag', label: 'batch: tag to add', terminal: true, by: 'row tags' },   { sel: '#bTagAdd', label: 'batch: add the tag to all', terminal: true, by: 'row tags' },+  { sel: '#bEffort', label: 'batch: effort to set', terminal: true, by: 'effort' },+  { sel: '#bEffortSet', label: 'batch: set the effort on all', terminal: true, by: 'effort' },   { sel: '.bt-x', label: 'batch: remove a tag from all', terminal: true, by: 'row tags', key: c => c.ds.tag, pin: ['long-lead'] },   { sel: '#legendSecs', label: 'legend: fold or unfold the section chips', safe: true, twice: true },   { sel: '#tagClear', label: 'legend: clear the tag filter', safe: true },@@ -2937,6 +2939,27 @@ async function effortFlow(browser) {   await view(page, 'table');   check('a reload keeps every estimate, and the cleared one stays cleared', await cellText('b2') === '16h' && await cellText('b1') === 'not est.' && await cellText('s1') === '8h');   await shot(page, 'effort-table');++  // ── The batch panel sets (or clears) effort on every selected task at once (0.63.1) ──+  await view(page, 'gantt'); await clearSel(page);+  await clickBar(page, 'b1', ['Control']); await clickBar(page, 'b2', ['Control']); await settle(page, 200);+  check('the batch panel has an effort row and says where the selection stands', await page.$eval('#bEffSum', e => e.textContent) === '1 of 2 tasks estimated, 16h in all', await page.$eval('#bEffSum', e => e.textContent).catch(() => 'no #bEffSum'));+  n0 = (await effLog()).length;+  await page.click('#bEffort'); await page.keyboard.type('4'); await page.keyboard.press('Enter'); await settle(page, 400);+  check('Enter sets 4h on both selected tasks', (await row('b1')).effort === 4 && (await row('b2')).effort === 4, JSON.stringify([await row('b1'), await row('b2')]));+  check('the panel stays open and its summary follows', await editorShown(page) && await page.$eval('#bEffSum', e => e.textContent) === '2 of 2 tasks estimated, 8h in all');+  L = (await effLog()).slice(n0);+  check('two `effort` lines via batch-editor', L.length === 2 && L.every(e => e.via === 'batch-editor' && e.to.effort === 4), JSON.stringify(L));+  await page.$eval('#bEffort', e => e.blur());+  await undo(page); await settle(page, 400);+  check('one undo puts both back (not estimated, 16h)', !('effort' in (await row('b1'))) && (await row('b2')).effort === 16, JSON.stringify([await row('b1'), await row('b2')]));+  await clearSel(page);+  await clickBar(page, 'b1', ['Control']); await clickBar(page, 'b2', ['Control']); await settle(page, 200);+  await page.click('#bEffort'); await page.keyboard.type('soon'); await page.click('#bEffortSet'); await settle(page, 300);+  check('a bad value is refused with a reason, nothing changes', /Effort is hours/.test(await page.$eval('#bEffErr', e => e.textContent)) && (await row('b2')).effort === 16);+  await page.$eval('#bEffort', e => { e.value = ''; }); await page.click('#bEffortSet'); await settle(page, 400);+  check('an empty box clears the estimate on all of them', !('effort' in (await row('b1'))) && !('effort' in (await row('b2'))));+  await clearSel(page);   await page.close(); }