diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e0fc0f..8fe678d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,26 @@
# Changelog
+## Draggable start on overdue and due-today bars (2026-06-22)
+
+### Fixed
+- Overdue and due-today task bars no longer collapse to a fixed today box; they show the real start date through today so the left resize ear can change the start date on the chart.
+
+## Independent date editing (2026-06-22)
+
+### Changed
+- Removed sort-by-start-date controls and automatic parent/subtask date coupling (no more expanding or clipping dates across levels).
+- Start and end dates on a project, task, or subtask no longer snap together when edited; each field updates on its own.
+- Projects, tasks, and subtasks can each have start and end dates extended or shortened independently via the detail sheet, bar menu, or gantt drag handles.
+
+## Parent task priority over subtasks (2026-06-22)
+
+### Changed
+- Parent tasks keep their own t-shirt size in the detail sheet; subtask sizes no longer replace it.
+- Press Enter in the subtask input on the detail sheet to add a subtask without clicking Add.
+- Detail pop-ups for projects, tasks, and subtasks include a Save button that flushes pending edits, persists, and closes the sheet.
+- Clicking the chart background inside a project opens that project's detail sheet and focuses the new-task input.
+- Projects still roll up dates and size points across all descendant leaves.
+
## Rebase PR #14 onto main (2026-06-19)
### Fixed
diff --git a/index.html b/index.html
index 2e540b4..554fd0a 100644
--- a/index.html
+++ b/index.html
@@ -202,7 +202,11 @@
background-repeat:no-repeat;background-position:right 5px center;background-size:15px}
.tbox .frow select:hover{background-color:#f0f2f6}
.tbox .danger{color:var(--red);background:var(--red-soft);border-radius:11px;padding:12px 18px;
- font-weight:700;font-size:14px;margin-top:18px;width:100%}
+ font-weight:700;font-size:14px;margin-top:10px;width:100%}
+ .tbox .dsave{width:100%;margin-top:18px;padding:12px 18px;border-radius:11px;background:var(--accent);
+ color:#fff;font-weight:700;font-size:15px}
+ .tbox .dsave:active{transform:scale(.98)}
+ .tbox .dsave.saved{background:var(--green)}
.closex{position:absolute;top:14px;right:16px;width:36px;height:36px;
border-radius:50%;background:#eef0f4;color:var(--ink-2);font-size:16px;font-weight:700;z-index:1}
.searchwrap{position:relative;flex:1;max-width:430px}
@@ -278,6 +282,15 @@
/* project group: a thin summary bar spanning the project's task range, then its tasks */
.pgroup{position:relative;margin-top:22px;padding:2px 0 8px}
.pgroup:first-of-type{margin-top:0;padding-top:0}
+ /* clickable project wash — empty chart space opens the project to add tasks */
+ .pgclick{position:absolute;inset:0;z-index:0;cursor:pointer;border:none;padding:0;background:transparent;font:inherit}
+ .pgwash{position:absolute;top:0;bottom:0;background:rgba(59,110,246,.05);border-radius:10px;
+ transition:background .15s;pointer-events:none}
+ .pgclick:hover .pgwash{background:rgba(59,110,246,.1)}
+ .pgroup>.grow{position:relative;z-index:1;pointer-events:none}
+ .pgroup .gtrack{pointer-events:none}
+ .pgroup .gbar,.pgroup .gsumlbl,.pgroup .gsumpct,.pgroup .gsumline,.pgroup .gsumfill,
+ .pgroup .gexp,.pgroup .gexpw,.pgroup .ear,.pgroup .gdot,.pgroup .gttlout{pointer-events:auto}
/* project header: name on its own line, the progress bar on the line below it */
/* top-align so the row's extra height (the gap before the first task) stays BELOW the bar,
instead of being centered and pushing the project name down away from the ribbon */
diff --git a/src/app/main.js b/src/app/main.js
index 3281b36..48019e6 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -241,7 +241,7 @@ let dayN, dayIso, barSpan, workDays, barColor, barGeom,
rollupSpan, spanFor, leafWeight, progWD, isUrgent, fmtD, commitBarDrag;
function syncDateHelpers() {
({ dayN, dayIso, barSpan, workDays, barColor, barGeom,
- rollupSpan, spanFor, leafWeight, progWD, isUrgent, fmtD, commitBarDrag } = createDateHelpers(TODAY));
+ rollupSpan, spanFor, leafWeight, progWD, isUrgent, fmtD, commitBarDrag } = createDateHelpers(TODAY, () => DATA));
}
syncDateHelpers();
@@ -275,8 +275,8 @@ function scheduleTodayRefresh() {
}
/* bars carry only FOUR urgency colors (owner identity is in the bubble) */
/* vivid, candy-bright status palette (like the reference) */
-/* a parent task's bar always spans its subtasks (start = earliest sub start, due = latest sub due),
- so a parent can never appear to start after its own subtasks */
+/* a parent task's bar uses its own dates/size; subtask edits do not change it.
+ Projects still roll up across all descendant leaves. */
/* effort weight = working days (Mon–Fri) inside a leaf's bar span, so the team doesn't have
to size every item — a longer subtask simply weighs more. A set size still counts, because
size feeds the bar's span via LEAD, which feeds this. Min 1 so a single-day or weekend-only
@@ -290,9 +290,9 @@ function scheduleTodayRefresh() {
let TW = 2.2, SPAN_EFFV = SPAN_G + 1.2;
const uDay=t=>t<0?t:(t<1?t*TW:TW+(t-1)); // day → stretched-day units
const gx=t=>(uDay(t)-R0G)/SPAN_EFFV*100; // day → % position on the track
-/* a due date means END of that day, and open work never lives in the past:
+/* a due date means END of that day:
— done tasks keep their historical span
- — late and due-today tasks ALL span exactly the today box, ending ON the today line
+ — overdue / due-today tasks show their real start through today so resize ears work
— future tasks start today at the earliest and end at the end of their due day */
/* re-renders triggered by clicks are deferred out of the input event: mutating the DOM
while Chrome is still dispatching the click can wedge its hover/input pipeline
@@ -489,12 +489,22 @@ function renderGantt(){
let pPts=0; flat([p],x=>{ if(x.children.length) return; pPts+=sizePts(x.size); });
const ph=Math.max(7,Math.min(20,Math.round(5+Math.sqrt(pPts)*2.2)));
rows.push(`
+
-
-
-
+
+
+
${taskRows}
`);
});
@@ -884,25 +894,19 @@ function refreshBarMenu(id){ if(BARMENU===id) openBarMenu(id); }
function closeBarMenu(){ BM.classList.remove("show"); BARMENU=null; }
document.addEventListener("pointerdown",e=>{ if(BARMENU&&!e.target.closest("#barMenu")) closeBarMenu(); },true);
-function syncTaskDates(n,field){
- if(!n.start||!n.due) return;
- const s=dayN(n.start), e=dayN(n.due);
- if(isNaN(s)||isNaN(e)||s<=e) return;
- if(field==="start") n.due=n.start; else n.start=n.due;
-}
-function updTask(id,f,v,quiet){ snap(); const n=findPath(id).pop();
+function updTask(id,f,v,quiet){ snap(); const path=findPath(id); const n=path.pop();
if(f==="title") n.title=v.trim()||n.title;
else if(f==="owner") n.owner=v;
else if(f==="priority") n.priority=v;
- else if(f==="due"){ n.due=v||null; syncTaskDates(n,"due"); }
- else if(f==="start"){ n.start=v||null; syncTaskDates(n,"start"); }
+ else if(f==="due") n.due=v||null;
+ else if(f==="start") n.start=v||null;
else if(f==="size") n.size=v?normalizeSize(v):null;
else if(f==="comment") n.comment=v.trim()||null;
renderAll(); if(!quiet&&f!=="title"&&f!=="comment") openDetail(id); }
function deleteTask(id){ const n=findPath(id).pop();
if(typeof confirm!=="undefined"&&!confirm('Delete "'+n.title+'"'+(n.children.length?" and its subtasks":"")+"?")) return;
snap(); detach(id); closeSheet(); renderAll(); }
-function addChild(id){
+function addChild(id, quiet){
const el=document.getElementById("dSubNew");
if(!el) return;
const v=el.value.trim();
@@ -918,9 +922,30 @@ function addChild(id){
n.open=true;
if(path.length>1&&!subOpen(id)){ if(subsAll) COL.delete(id); else EXP.add(id); } // show new subtask rows on the gantt
renderAll();
+ if(quiet) return child;
openDetail(id,{reveal:"bottom"});
+ requestAnimationFrame(()=>document.getElementById("dSubNew")?.focus());
revealGanttTask(child.id);
}
+function saveDetail(){
+ if(DETAIL_ID==null) return;
+ const id=DETAIL_ID;
+ snap();
+ const path=findPath(id);
+ if(!path) return;
+ const n=path[path.length-1];
+ const titleEl=document.getElementById("dTitle");
+ if(titleEl){ const t=titleEl.value.trim(); if(t) n.title=t; }
+ const commentEl=document.querySelector(".dcomment");
+ if(commentEl) n.comment=commentEl.value.trim()||null;
+ if(document.getElementById("dSubNew")?.value.trim()) addChild(id,true);
+ requestSave();
+ renderAll();
+ const btn=document.querySelector(".dsave");
+ if(btn){ btn.textContent="Saved ✓"; btn.classList.add("saved"); btn.disabled=true; }
+ ding(1);
+ setTimeout(closeSheet,400);
+}
function addProject(){
snap();
const proj=T("New project","fd",{d:dayIso(LEAD.l),open:true});
@@ -948,6 +973,9 @@ function revealGanttTask(id){
if(typeof requestAnimationFrame!=="undefined") requestAnimationFrame(()=>requestAnimationFrame(run));
else run();
}
+function openProjectChart(id){
+ openDetail(id,{focusTask:true,reveal:"bottom"});
+}
function openDetail(id,opts){
const path=findPath(id);
if(!path){ if(DETAIL_ID===id) closeSheet(); DETAIL_ID=null; return; }
@@ -966,10 +994,10 @@ function openDetail(id,opts){
flat(DATA,(x,depth)=>{ if(contains(n,x.id))return;
if(depth+1+heightOf(n)>2) return; // keep the 3-level hierarchy
mopts.push(``); });
- // Size: leaves carry an editable t-shirt size; projects/parent tasks SHOW the rolled-up
- // point total (sum of their leaves' size points) — not a field the user fills in.
+ // Size: subtasks and parent tasks carry an editable t-shirt size; projects show rolled-up points.
let _szPts=0; flat([n],x=>{ if(x.children.length) return; _szPts+=sizePts(x.size); });
- const sizeFld=leaf
+ const sizeEditable=leaf||path.length===2;
+ const sizeFld=sizeEditable
? `
Size
`
: `
Size${_szPts} pts
`;
@@ -989,10 +1017,7 @@ function openDetail(id,opts){
Move to
${path.length>=3?"":`
${path.length>1?"Subtasks":"Tasks — grip ⠿ to drag onto another project"}