@agimon-ai/doompi-task 0.0.1-alpha.0 → 0.0.1-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,7 +15,21 @@ interface TaskGroups {
15
15
  completed: Task[];
16
16
  failed: Task[];
17
17
  }
18
+ interface TaskProjection {
19
+ visible: Task[];
20
+ counts: TaskCounts;
21
+ groups: TaskGroups;
22
+ showIds: boolean;
23
+ hasActiveWork: boolean;
24
+ }
18
25
  declare function visibleTasks(tasks: readonly Task[]): Task[];
26
+ /**
27
+ * Derive all overlay-facing state in O(tasks + dependency edges).
28
+ *
29
+ * The id index replaces one full task-list scan per dependency, while the
30
+ * ordered pass keeps group ordering identical to the source document.
31
+ */
32
+ declare function deriveTaskProjection(tasks: readonly Task[]): TaskProjection;
19
33
  declare function countTasks(tasks: readonly Task[]): TaskCounts;
20
34
  /**
21
35
  * Group tasks for display.
@@ -32,14 +46,8 @@ interface OverlayLayout {
32
46
  hiddenCompleted: number;
33
47
  truncatedTail: number;
34
48
  }
35
- /**
36
- * Choose which rows fit the overlay budget.
37
- *
38
- * Active work wins the space: in-progress first, then blocked and pending, and
39
- * completed rows are dropped first when the budget runs short since they carry
40
- * the least information about what happens next.
41
- */
49
+ declare function selectOverlayLayoutFromProjection(projection: TaskProjection, budget: number): OverlayLayout;
42
50
  declare function selectOverlayLayout(tasks: readonly Task[], budget: number): OverlayLayout;
43
51
  //#endregion
44
- export { OverlayLayout, TaskCounts, TaskGroups, countTasks, groupTasks, hasActiveWork, selectOverlayLayout, shouldShowIds, visibleTasks };
52
+ export { OverlayLayout, TaskCounts, TaskGroups, TaskProjection, countTasks, deriveTaskProjection, groupTasks, hasActiveWork, selectOverlayLayout, selectOverlayLayoutFromProjection, shouldShowIds, visibleTasks };
45
53
  //# sourceMappingURL=selectors.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"selectors.d.mts","names":[],"sources":["../../src/tui/selectors.ts"],"mappings":";;UAGiB;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf,YAAY;EACZ,SAAS;EACT,SAAS;EACT,WAAW;EACX,QAAQ;;iBAGM,aAAa,gBAAgB,SAAS;iBAItC,WAAW,gBAAgB,SAAS;;;;;;;iBAkBpC,WAAW,gBAAgB,SAAS;;iBAYpC,cAAc,gBAAgB;iBAI9B,cAAc,gBAAgB;UAI7B;EACf,SAAS;EACT;EACA;;;;;;;;;iBAUc,oBAAoB,gBAAgB,QAAQ,iBAAiB"}
1
+ {"version":3,"file":"selectors.d.mts","names":[],"sources":["../../src/tui/selectors.ts"],"mappings":";;UAEiB;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf,YAAY;EACZ,SAAS;EACT,SAAS;EACT,WAAW;EACX,QAAQ;;UAGO;EACf,SAAS;EACT,QAAQ;EACR,QAAQ;EACR;EACA;;iBAGc,aAAa,gBAAgB,SAAS;;;;;;;iBA6BtC,qBAAqB,gBAAgB,SAAS;iBA2C9C,WAAW,gBAAgB,SAAS;;;;;;;iBAUpC,WAAW,gBAAgB,SAAS;;iBAKpC,cAAc,gBAAgB;iBAI9B,cAAc,gBAAgB;UAI7B;EACf,SAAS;EACT;EACA;;iBAyCc,kCAAkC,YAAY,gBAAgB,iBAAiB;iBAI/E,oBAAoB,gBAAgB,QAAQ,iBAAiB"}
@@ -1,2 +1,2 @@
1
- import{isBlocked as e}from"../store/taskGraph.mjs";function t(e){return e.filter(e=>e.status!==`deleted`)}function n(n){let r=t(n);return{total:r.length,completed:r.filter(e=>e.status===`completed`).length,inProgress:r.filter(e=>e.status===`in_progress`).length,pending:r.filter(e=>e.status===`pending`).length,failed:r.filter(e=>e.status===`failed`).length,blocked:r.filter(t=>t.status===`pending`&&e(n,t)).length}}function r(n){let r=t(n);return{inProgress:r.filter(e=>e.status===`in_progress`),blocked:r.filter(t=>t.status===`pending`&&e(n,t)),pending:r.filter(t=>t.status===`pending`&&!e(n,t)),failed:r.filter(e=>e.status===`failed`),completed:r.filter(e=>e.status===`completed`)}}function i(e){return t(e).some(e=>!!e.blockedBy?.length)}function a(e){return t(e).some(e=>e.status===`in_progress`)}function o(e,t){let n=r(e),i=[...n.inProgress,...n.failed,...n.blocked,...n.pending,...n.completed];if(t<=0)return{visible:[],hiddenCompleted:n.completed.length,truncatedTail:i.length-n.completed.length};if(i.length<=t)return{visible:i,hiddenCompleted:0,truncatedTail:0};let a=i.filter(e=>e.status!==`completed`);if(a.length<=t){let e=t-a.length,r=n.completed.slice(0,e);return{visible:[...a,...r],hiddenCompleted:n.completed.length-r.length,truncatedTail:0}}return{visible:a.slice(0,t),hiddenCompleted:n.completed.length,truncatedTail:a.length-t}}export{n as countTasks,r as groupTasks,a as hasActiveWork,o as selectOverlayLayout,i as shouldShowIds,t as visibleTasks};
1
+ function e(e){return e.filter(e=>e.status!==`deleted`)}function t(e){let t=new Map;for(let n of e)t.has(n.id)||t.set(n.id,n.status);return t}function n(e,t){return e.blockedBy?.some(e=>{let n=t.get(e);return n!==void 0&&n!==`completed`&&n!==`deleted`})??!1}function r(e){let r=t(e),i=[],a={total:0,completed:0,inProgress:0,pending:0,failed:0,blocked:0},o={inProgress:[],blocked:[],pending:[],completed:[],failed:[]},s=!1;for(let t of e)if(t.status!==`deleted`)switch(i.push(t),a.total+=1,s||=!!t.blockedBy?.length,t.status){case`in_progress`:a.inProgress+=1,o.inProgress.push(t);break;case`pending`:a.pending+=1,n(t,r)?(a.blocked+=1,o.blocked.push(t)):o.pending.push(t);break;case`failed`:a.failed+=1,o.failed.push(t);break;case`completed`:a.completed+=1,o.completed.push(t)}return{visible:i,counts:a,groups:o,showIds:s,hasActiveWork:a.inProgress>0}}function i(e){return r(e).counts}function a(e){return r(e).groups}function o(e){return e.some(e=>e.status!==`deleted`&&!!e.blockedBy?.length)}function s(e){return e.some(e=>e.status===`in_progress`)}function c(e,t){let n=[...e.inProgress,...e.failed,...e.blocked,...e.pending],r=[...n,...e.completed];if(t<=0)return{visible:[],hiddenCompleted:e.completed.length,truncatedTail:n.length};if(r.length<=t)return{visible:r,hiddenCompleted:0,truncatedTail:0};if(n.length<=t){let r=t-n.length,i=e.completed.slice(0,r);return{visible:[...n,...i],hiddenCompleted:e.completed.length-i.length,truncatedTail:0}}return{visible:n.slice(0,t),hiddenCompleted:e.completed.length,truncatedTail:n.length-t}}function l(e,t){return c(e.groups,t)}function u(e,t){return l(r(e),t)}export{i as countTasks,r as deriveTaskProjection,a as groupTasks,s as hasActiveWork,u as selectOverlayLayout,l as selectOverlayLayoutFromProjection,o as shouldShowIds,e as visibleTasks};
2
2
  //# sourceMappingURL=selectors.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"selectors.mjs","names":[],"sources":["../../src/tui/selectors.ts"],"sourcesContent":["import { isBlocked } from '../store/taskGraph.ts';\nimport type { Task } from '../store/types.ts';\n\nexport interface TaskCounts {\n total: number;\n completed: number;\n inProgress: number;\n pending: number;\n failed: number;\n blocked: number;\n}\n\nexport interface TaskGroups {\n inProgress: Task[];\n blocked: Task[];\n pending: Task[];\n completed: Task[];\n failed: Task[];\n}\n\nexport function visibleTasks(tasks: readonly Task[]): Task[] {\n return tasks.filter((task) => task.status !== 'deleted');\n}\n\nexport function countTasks(tasks: readonly Task[]): TaskCounts {\n const visible = visibleTasks(tasks);\n return {\n total: visible.length,\n completed: visible.filter((task) => task.status === 'completed').length,\n inProgress: visible.filter((task) => task.status === 'in_progress').length,\n pending: visible.filter((task) => task.status === 'pending').length,\n failed: visible.filter((task) => task.status === 'failed').length,\n blocked: visible.filter((task) => task.status === 'pending' && isBlocked(tasks, task)).length,\n };\n}\n\n/**\n * Group tasks for display.\n *\n * Blocked tasks split out of `pending` because a reader scanning the overlay\n * needs to know at a glance what is actually actionable right now.\n */\nexport function groupTasks(tasks: readonly Task[]): TaskGroups {\n const visible = visibleTasks(tasks);\n return {\n inProgress: visible.filter((task) => task.status === 'in_progress'),\n blocked: visible.filter((task) => task.status === 'pending' && isBlocked(tasks, task)),\n pending: visible.filter((task) => task.status === 'pending' && !isBlocked(tasks, task)),\n failed: visible.filter((task) => task.status === 'failed'),\n completed: visible.filter((task) => task.status === 'completed'),\n };\n}\n\n/** Ids are only worth the visual noise once dependencies are in play. */\nexport function shouldShowIds(tasks: readonly Task[]): boolean {\n return visibleTasks(tasks).some((task) => Boolean(task.blockedBy?.length));\n}\n\nexport function hasActiveWork(tasks: readonly Task[]): boolean {\n return visibleTasks(tasks).some((task) => task.status === 'in_progress');\n}\n\nexport interface OverlayLayout {\n visible: Task[];\n hiddenCompleted: number;\n truncatedTail: number;\n}\n\n/**\n * Choose which rows fit the overlay budget.\n *\n * Active work wins the space: in-progress first, then blocked and pending, and\n * completed rows are dropped first when the budget runs short since they carry\n * the least information about what happens next.\n */\nexport function selectOverlayLayout(tasks: readonly Task[], budget: number): OverlayLayout {\n const groups = groupTasks(tasks);\n const ordered = [...groups.inProgress, ...groups.failed, ...groups.blocked, ...groups.pending, ...groups.completed];\n if (budget <= 0) {\n return {\n visible: [],\n hiddenCompleted: groups.completed.length,\n truncatedTail: ordered.length - groups.completed.length,\n };\n }\n if (ordered.length <= budget) {\n return { visible: ordered, hiddenCompleted: 0, truncatedTail: 0 };\n }\n\n const withoutCompleted = ordered.filter((task) => task.status !== 'completed');\n if (withoutCompleted.length <= budget) {\n const remaining = budget - withoutCompleted.length;\n const shownCompleted = groups.completed.slice(0, remaining);\n return {\n visible: [...withoutCompleted, ...shownCompleted],\n hiddenCompleted: groups.completed.length - shownCompleted.length,\n truncatedTail: 0,\n };\n }\n\n return {\n visible: withoutCompleted.slice(0, budget),\n hiddenCompleted: groups.completed.length,\n truncatedTail: withoutCompleted.length - budget,\n };\n}\n"],"mappings":"mDAoBA,SAAgB,EAAa,EAAgC,CAC3D,OAAO,EAAM,OAAQ,GAAS,EAAK,SAAW,SAAS,CACzD,CAEA,SAAgB,EAAW,EAAoC,CAC7D,IAAM,EAAU,EAAa,CAAK,EAClC,MAAO,CACL,MAAO,EAAQ,OACf,UAAW,EAAQ,OAAQ,GAAS,EAAK,SAAW,WAAW,CAAC,CAAC,OACjE,WAAY,EAAQ,OAAQ,GAAS,EAAK,SAAW,aAAa,CAAC,CAAC,OACpE,QAAS,EAAQ,OAAQ,GAAS,EAAK,SAAW,SAAS,CAAC,CAAC,OAC7D,OAAQ,EAAQ,OAAQ,GAAS,EAAK,SAAW,QAAQ,CAAC,CAAC,OAC3D,QAAS,EAAQ,OAAQ,GAAS,EAAK,SAAW,WAAa,EAAU,EAAO,CAAI,CAAC,CAAC,CAAC,MACzF,CACF,CAQA,SAAgB,EAAW,EAAoC,CAC7D,IAAM,EAAU,EAAa,CAAK,EAClC,MAAO,CACL,WAAY,EAAQ,OAAQ,GAAS,EAAK,SAAW,aAAa,EAClE,QAAS,EAAQ,OAAQ,GAAS,EAAK,SAAW,WAAa,EAAU,EAAO,CAAI,CAAC,EACrF,QAAS,EAAQ,OAAQ,GAAS,EAAK,SAAW,WAAa,CAAC,EAAU,EAAO,CAAI,CAAC,EACtF,OAAQ,EAAQ,OAAQ,GAAS,EAAK,SAAW,QAAQ,EACzD,UAAW,EAAQ,OAAQ,GAAS,EAAK,SAAW,WAAW,CACjE,CACF,CAGA,SAAgB,EAAc,EAAiC,CAC7D,OAAO,EAAa,CAAK,CAAC,CAAC,KAAM,GAAS,EAAQ,EAAK,WAAW,MAAO,CAC3E,CAEA,SAAgB,EAAc,EAAiC,CAC7D,OAAO,EAAa,CAAK,CAAC,CAAC,KAAM,GAAS,EAAK,SAAW,aAAa,CACzE,CAeA,SAAgB,EAAoB,EAAwB,EAA+B,CACzF,IAAM,EAAS,EAAW,CAAK,EACzB,EAAU,CAAC,GAAG,EAAO,WAAY,GAAG,EAAO,OAAQ,GAAG,EAAO,QAAS,GAAG,EAAO,QAAS,GAAG,EAAO,SAAS,EAClH,GAAI,GAAU,EACZ,MAAO,CACL,QAAS,CAAC,EACV,gBAAiB,EAAO,UAAU,OAClC,cAAe,EAAQ,OAAS,EAAO,UAAU,MACnD,EAEF,GAAI,EAAQ,QAAU,EACpB,MAAO,CAAE,QAAS,EAAS,gBAAiB,EAAG,cAAe,CAAE,EAGlE,IAAM,EAAmB,EAAQ,OAAQ,GAAS,EAAK,SAAW,WAAW,EAC7E,GAAI,EAAiB,QAAU,EAAQ,CACrC,IAAM,EAAY,EAAS,EAAiB,OACtC,EAAiB,EAAO,UAAU,MAAM,EAAG,CAAS,EAC1D,MAAO,CACL,QAAS,CAAC,GAAG,EAAkB,GAAG,CAAc,EAChD,gBAAiB,EAAO,UAAU,OAAS,EAAe,OAC1D,cAAe,CACjB,CACF,CAEA,MAAO,CACL,QAAS,EAAiB,MAAM,EAAG,CAAM,EACzC,gBAAiB,EAAO,UAAU,OAClC,cAAe,EAAiB,OAAS,CAC3C,CACF"}
1
+ {"version":3,"file":"selectors.mjs","names":[],"sources":["../../src/tui/selectors.ts"],"sourcesContent":["import type { Task } from '../store/types.ts';\n\nexport interface TaskCounts {\n total: number;\n completed: number;\n inProgress: number;\n pending: number;\n failed: number;\n blocked: number;\n}\n\nexport interface TaskGroups {\n inProgress: Task[];\n blocked: Task[];\n pending: Task[];\n completed: Task[];\n failed: Task[];\n}\n\nexport interface TaskProjection {\n visible: Task[];\n counts: TaskCounts;\n groups: TaskGroups;\n showIds: boolean;\n hasActiveWork: boolean;\n}\n\nexport function visibleTasks(tasks: readonly Task[]): Task[] {\n return tasks.filter((task) => task.status !== 'deleted');\n}\n\nfunction indexStatuses(tasks: readonly Task[]): Map<number, Task['status']> {\n const statuses = new Map<number, Task['status']>();\n for (const task of tasks) {\n // Valid stores have unique ids. Keeping the first entry also preserves the\n // former `Array.find` behavior if a corrupt document contains duplicates.\n if (!statuses.has(task.id)) statuses.set(task.id, task.status);\n }\n return statuses;\n}\n\nfunction hasUnresolvedBlocker(task: Task, statuses: ReadonlyMap<number, Task['status']>): boolean {\n return (\n task.blockedBy?.some((id) => {\n const status = statuses.get(id);\n return status !== undefined && status !== 'completed' && status !== 'deleted';\n }) ?? false\n );\n}\n\n/**\n * Derive all overlay-facing state in O(tasks + dependency edges).\n *\n * The id index replaces one full task-list scan per dependency, while the\n * ordered pass keeps group ordering identical to the source document.\n */\nexport function deriveTaskProjection(tasks: readonly Task[]): TaskProjection {\n const statuses = indexStatuses(tasks);\n const visible: Task[] = [];\n const counts: TaskCounts = { total: 0, completed: 0, inProgress: 0, pending: 0, failed: 0, blocked: 0 };\n const groups: TaskGroups = { inProgress: [], blocked: [], pending: [], completed: [], failed: [] };\n let showIds = false;\n\n for (const task of tasks) {\n if (task.status === 'deleted') continue;\n\n visible.push(task);\n counts.total += 1;\n showIds ||= Boolean(task.blockedBy?.length);\n\n switch (task.status) {\n case 'in_progress':\n counts.inProgress += 1;\n groups.inProgress.push(task);\n break;\n case 'pending': {\n counts.pending += 1;\n if (hasUnresolvedBlocker(task, statuses)) {\n counts.blocked += 1;\n groups.blocked.push(task);\n } else {\n groups.pending.push(task);\n }\n break;\n }\n case 'failed':\n counts.failed += 1;\n groups.failed.push(task);\n break;\n case 'completed':\n counts.completed += 1;\n groups.completed.push(task);\n break;\n }\n }\n\n return { visible, counts, groups, showIds, hasActiveWork: counts.inProgress > 0 };\n}\n\nexport function countTasks(tasks: readonly Task[]): TaskCounts {\n return deriveTaskProjection(tasks).counts;\n}\n\n/**\n * Group tasks for display.\n *\n * Blocked tasks split out of `pending` because a reader scanning the overlay\n * needs to know at a glance what is actually actionable right now.\n */\nexport function groupTasks(tasks: readonly Task[]): TaskGroups {\n return deriveTaskProjection(tasks).groups;\n}\n\n/** Ids are only worth the visual noise once dependencies are in play. */\nexport function shouldShowIds(tasks: readonly Task[]): boolean {\n return tasks.some((task) => task.status !== 'deleted' && Boolean(task.blockedBy?.length));\n}\n\nexport function hasActiveWork(tasks: readonly Task[]): boolean {\n return tasks.some((task) => task.status === 'in_progress');\n}\n\nexport interface OverlayLayout {\n visible: Task[];\n hiddenCompleted: number;\n truncatedTail: number;\n}\n\n/**\n * Choose which rows fit the overlay budget.\n *\n * Active work wins the space: in-progress first, then blocked and pending, and\n * completed rows are dropped first when the budget runs short since they carry\n * the least information about what happens next.\n */\nfunction selectLayout(groups: TaskGroups, budget: number): OverlayLayout {\n const active = [...groups.inProgress, ...groups.failed, ...groups.blocked, ...groups.pending];\n const ordered = [...active, ...groups.completed];\n if (budget <= 0) {\n return {\n visible: [],\n hiddenCompleted: groups.completed.length,\n truncatedTail: active.length,\n };\n }\n if (ordered.length <= budget) {\n return { visible: ordered, hiddenCompleted: 0, truncatedTail: 0 };\n }\n\n if (active.length <= budget) {\n const remaining = budget - active.length;\n const shownCompleted = groups.completed.slice(0, remaining);\n return {\n visible: [...active, ...shownCompleted],\n hiddenCompleted: groups.completed.length - shownCompleted.length,\n truncatedTail: 0,\n };\n }\n\n return {\n visible: active.slice(0, budget),\n hiddenCompleted: groups.completed.length,\n truncatedTail: active.length - budget,\n };\n}\n\nexport function selectOverlayLayoutFromProjection(projection: TaskProjection, budget: number): OverlayLayout {\n return selectLayout(projection.groups, budget);\n}\n\nexport function selectOverlayLayout(tasks: readonly Task[], budget: number): OverlayLayout {\n return selectOverlayLayoutFromProjection(deriveTaskProjection(tasks), budget);\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../config.cjs"),t=require("./format.cjs"),n=require("./selectors.cjs");let r=require("@earendil-works/pi-tui");const i=`doom-tasks`;var a=class{options;uiCtx;widgetRegistered=!1;tui;progressTimer;collapsed=!1;constructor(e){this.options=e}setUICtx(e){e!==this.uiCtx&&(this.uiCtx&&this.widgetRegistered&&this.uiCtx.setWidget(i,void 0),this.stopProgressTimer(),this.uiCtx=e,this.widgetRegistered=!1,this.tui=void 0)}update(){if(!this.uiCtx){this.stopProgressTimer();return}let e=n.visibleTasks(this.options.getTasks());if(e.length===0){this.widgetRegistered&&(this.uiCtx.setWidget(i,void 0),this.widgetRegistered=!1,this.tui=void 0),this.stopProgressTimer();return}if(this.widgetRegistered){this.syncProgressTimer(e),this.tui?.requestRender();return}this.uiCtx.setWidget(i,(e,t)=>(this.tui=e,{render:e=>this.renderWidget(this.uiCtx?.theme??t,e),invalidate:()=>{}}),{placement:`aboveEditor`}),this.widgetRegistered=!0,this.syncProgressTimer(e)}syncProgressTimer(e){if(!e.some(e=>e.delegation?.state===`running`)){this.stopProgressTimer();return}this.progressTimer||(this.progressTimer=setInterval(()=>this.tui?.requestRender(),1e3),this.progressTimer.unref?.())}stopProgressTimer(){this.progressTimer&&clearInterval(this.progressTimer),this.progressTimer=void 0}toggleCollapse(){this.collapsed=!this.collapsed,this.tui?.requestRender(!0)}isRegistered(){return this.widgetRegistered}renderWidget(i,a){let o=n.visibleTasks(this.options.getTasks());if(o.length===0)return[];let s=e=>(0,r.truncateToWidth)(e,a,`…`),c=n.countTasks(o),l=n.hasActiveWork(o),u=l?`accent`:`dim`,d=l?`●`:`○`,f=`Tasks (${c.completed}/${c.total})`;c.failed>0&&(f+=` · ${c.failed} failed`);let p=s(`${i.fg(u,d)} ${i.fg(u,f)}`);if(this.collapsed){let t=e.resolveCollapseKey(),n=t===`off`?`collapsed`:`${t} to expand`;return this.withTrailingSpacer([p,s(`${i.fg(`dim`,`└─`)} ${i.fg(`dim`,n)}`)])}let m=n.shouldShowIds(o),h=n.selectOverlayLayout(o,e.getMaxWidgetLines()-1),g=[p];for(let e of h.visible){let n=this.options.delegation.progressFor(e);g.push(s(`${i.fg(`dim`,`├─`)} ${t.formatOverlayTaskLine(e,i,m,n)}`))}let _=h.hiddenCompleted+h.truncatedTail;if(_===0){let e=g.length-1;return g[e]=g[e].replace(`├─`,`└─`),this.withTrailingSpacer(g)}let v=[];h.hiddenCompleted>0&&v.push(`${h.hiddenCompleted} completed`),h.truncatedTail>0&&v.push(`${h.truncatedTail} pending`);let y=v.length>0?`+${_} more (${v.join(`, `)})`:`+${_} more`;return g.push(s(`${i.fg(`dim`,`└─`)} ${i.fg(`dim`,y)}`)),this.withTrailingSpacer(g)}withTrailingSpacer(e){return e.length===0||e.push(``),e}dispose(){this.stopProgressTimer(),this.uiCtx&&this.uiCtx.setWidget(i,void 0),this.widgetRegistered=!1,this.tui=void 0,this.uiCtx=void 0,this.collapsed=!1}};exports.TaskOverlay=a;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../config.cjs"),t=require("./format.cjs"),n=require("./selectors.cjs");let r=require("@earendil-works/pi-tui");const i=`doom-tasks`;var a=class{options;uiCtx;widgetRegistered=!1;tui;progressTimer;collapsed=!1;constructor(e){this.options=e}setUICtx(e){e!==this.uiCtx&&(this.uiCtx&&this.widgetRegistered&&this.uiCtx.setWidget(i,void 0),this.stopProgressTimer(),this.uiCtx=e,this.widgetRegistered=!1,this.tui=void 0)}update(){if(!this.uiCtx){this.stopProgressTimer();return}let e=n.visibleTasks(this.options.getTasks());if(e.length===0){this.widgetRegistered&&(this.uiCtx.setWidget(i,void 0),this.widgetRegistered=!1,this.tui=void 0),this.stopProgressTimer();return}if(this.widgetRegistered){this.syncProgressTimer(e),this.tui?.requestRender();return}this.uiCtx.setWidget(i,(e,t)=>(this.tui=e,{render:e=>this.renderWidget(this.uiCtx?.theme??t,e),invalidate:()=>{}}),{placement:`aboveEditor`}),this.widgetRegistered=!0,this.syncProgressTimer(e)}syncProgressTimer(e){let t=e.some(e=>e.status!==`deleted`&&e.delegation?.state===`running`);if(this.collapsed||!this.widgetRegistered||!t){this.stopProgressTimer();return}this.progressTimer||(this.progressTimer=setInterval(()=>this.tui?.requestRender(),1e3),this.progressTimer.unref?.())}stopProgressTimer(){this.progressTimer&&clearInterval(this.progressTimer),this.progressTimer=void 0}toggleCollapse(){this.collapsed=!this.collapsed,this.syncProgressTimer(this.options.getTasks()),this.tui?.requestRender(!0)}isRegistered(){return this.widgetRegistered}renderWidget(i,a){let o=n.deriveTaskProjection(this.options.getTasks());if(o.visible.length===0)return[];let s=e=>(0,r.truncateToWidth)(e,a,`…`),c=o.hasActiveWork?`accent`:`dim`,l=o.hasActiveWork?`●`:`○`,u=`Tasks (${o.counts.completed}/${o.counts.total})`;o.counts.failed>0&&(u+=` · ${o.counts.failed} failed`);let d=s(`${i.fg(c,l)} ${i.fg(c,u)}`);if(this.collapsed){let t=e.resolveCollapseKey(),n=t===`off`?`collapsed`:`${t} to expand`;return this.withTrailingSpacer([d,s(`${i.fg(`dim`,`└─`)} ${i.fg(`dim`,n)}`)])}let f=n.selectOverlayLayoutFromProjection(o,e.getMaxWidgetLines()-1),p=[d];for(let e of f.visible){let n=this.options.delegation.progressFor(e);p.push(s(`${i.fg(`dim`,`├─`)} ${t.formatOverlayTaskLine(e,i,o.showIds,n)}`))}let m=f.hiddenCompleted+f.truncatedTail;if(m===0){let e=p.length-1;return p[e]=p[e].replace(`├─`,`└─`),this.withTrailingSpacer(p)}let h=[];f.hiddenCompleted>0&&h.push(`${f.hiddenCompleted} completed`),f.truncatedTail>0&&h.push(`${f.truncatedTail} pending`);let g=h.length>0?`+${m} more (${h.join(`, `)})`:`+${m} more`;return p.push(s(`${i.fg(`dim`,`└─`)} ${i.fg(`dim`,g)}`)),this.withTrailingSpacer(p)}withTrailingSpacer(e){return e.length===0||e.push(``),e}dispose(){this.stopProgressTimer(),this.uiCtx&&this.uiCtx.setWidget(i,void 0),this.widgetRegistered=!1,this.tui=void 0,this.uiCtx=void 0,this.collapsed=!1}};exports.TaskOverlay=a;
2
2
  //# sourceMappingURL=taskOverlay.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"taskOverlay.cjs","names":["visibleTasks","truncateToWidth","countTasks","hasActiveWork","resolveCollapseKey","shouldShowIds","selectOverlayLayout","getMaxWidgetLines","formatOverlayTaskLine"],"sources":["../../src/tui/taskOverlay.ts"],"sourcesContent":["import type { ExtensionUIContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { type TUI, truncateToWidth } from '@earendil-works/pi-tui';\nimport { COLLAPSE_KEY_OFF, getMaxWidgetLines, resolveCollapseKey } from '../config.ts';\nimport type { DelegationManager } from '../delegation/manager.ts';\nimport type { Task } from '../store/types.ts';\nimport { formatOverlayTaskLine } from './format.ts';\nimport { countTasks, hasActiveWork, selectOverlayLayout, shouldShowIds, visibleTasks } from './selectors.ts';\n\nconst WIDGET_KEY = 'doom-tasks';\nconst OVERLAY_HEADING = 'Tasks';\nconst PROGRESS_TICK_MS = 1000;\n\nexport interface TaskOverlayOptions {\n getTasks: () => readonly Task[];\n delegation: DelegationManager;\n}\n\n/**\n * Persistent widget listing tasks above the editor.\n *\n * Registered once per UI context and refreshed with `requestRender()`; it\n * auto-hides when there is nothing to show so an idle session keeps a clean\n * prompt.\n */\nexport class TaskOverlay {\n private readonly options: TaskOverlayOptions;\n private uiCtx: ExtensionUIContext | undefined;\n private widgetRegistered = false;\n private tui: TUI | undefined;\n private progressTimer: NodeJS.Timeout | undefined;\n private collapsed = false;\n\n constructor(options: TaskOverlayOptions) {\n this.options = options;\n }\n\n /** Identity-compare so repeated session_start handlers stay idempotent. */\n setUICtx(ctx: ExtensionUIContext): void {\n if (ctx === this.uiCtx) return;\n if (this.uiCtx && this.widgetRegistered) this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.stopProgressTimer();\n this.uiCtx = ctx;\n this.widgetRegistered = false;\n this.tui = undefined;\n }\n\n update(): void {\n if (!this.uiCtx) {\n this.stopProgressTimer();\n return;\n }\n const tasks = visibleTasks(this.options.getTasks());\n\n if (tasks.length === 0) {\n if (this.widgetRegistered) {\n this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.widgetRegistered = false;\n this.tui = undefined;\n }\n this.stopProgressTimer();\n return;\n }\n\n if (this.widgetRegistered) {\n this.syncProgressTimer(tasks);\n this.tui?.requestRender();\n return;\n }\n\n this.uiCtx.setWidget(\n WIDGET_KEY,\n (tui, factoryTheme) => {\n this.tui = tui;\n return {\n render: (width: number) => this.renderWidget(this.uiCtx?.theme ?? factoryTheme, width),\n invalidate: () => {\n // Nothing is cached: the next render reads the live theme and store.\n },\n };\n },\n { placement: 'aboveEditor' },\n );\n this.widgetRegistered = true;\n this.syncProgressTimer(tasks);\n }\n\n private syncProgressTimer(tasks: readonly Task[]): void {\n const hasRunningDelegation = tasks.some((task) => task.delegation?.state === 'running');\n if (!hasRunningDelegation) {\n this.stopProgressTimer();\n return;\n }\n if (this.progressTimer) return;\n this.progressTimer = setInterval(() => this.tui?.requestRender(), PROGRESS_TICK_MS);\n this.progressTimer.unref?.();\n }\n\n private stopProgressTimer(): void {\n if (this.progressTimer) clearInterval(this.progressTimer);\n this.progressTimer = undefined;\n }\n\n toggleCollapse(): void {\n this.collapsed = !this.collapsed;\n // Forced redraw: collapsing changes the widget height, not just its content.\n this.tui?.requestRender(true);\n }\n\n isRegistered(): boolean {\n return this.widgetRegistered;\n }\n\n private renderWidget(theme: Theme, width: number): string[] {\n const tasks = visibleTasks(this.options.getTasks());\n if (tasks.length === 0) return [];\n\n const truncate = (line: string): string => truncateToWidth(line, width, '…');\n const counts = countTasks(tasks);\n const active = hasActiveWork(tasks);\n\n const headingColor = active ? 'accent' : 'dim';\n const headingIcon = active ? '●' : '○';\n let headingText = `${OVERLAY_HEADING} (${counts.completed}/${counts.total})`;\n if (counts.failed > 0) headingText += ` · ${counts.failed} failed`;\n const heading = truncate(`${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, headingText)}`);\n\n if (this.collapsed) {\n const key = resolveCollapseKey();\n const hint = key === COLLAPSE_KEY_OFF ? 'collapsed' : `${key} to expand`;\n return this.withTrailingSpacer([heading, truncate(`${theme.fg('dim', '└─')} ${theme.fg('dim', hint)}`)]);\n }\n\n const showIds = shouldShowIds(tasks);\n const layout = selectOverlayLayout(tasks, getMaxWidgetLines() - 1);\n const lines = [heading];\n for (const task of layout.visible) {\n const progress = this.options.delegation.progressFor(task);\n lines.push(truncate(`${theme.fg('dim', '├─')} ${formatOverlayTaskLine(task, theme, showIds, progress)}`));\n }\n\n const hidden = layout.hiddenCompleted + layout.truncatedTail;\n if (hidden === 0) {\n const last = lines.length - 1;\n lines[last] = lines[last].replace('├─', '└─');\n return this.withTrailingSpacer(lines);\n }\n\n const parts: string[] = [];\n if (layout.hiddenCompleted > 0) parts.push(`${layout.hiddenCompleted} completed`);\n if (layout.truncatedTail > 0) parts.push(`${layout.truncatedTail} pending`);\n const summary = parts.length > 0 ? `+${hidden} more (${parts.join(', ')})` : `+${hidden} more`;\n lines.push(truncate(`${theme.fg('dim', '└─')} ${theme.fg('dim', summary)}`));\n return this.withTrailingSpacer(lines);\n }\n\n /** Pi adds a spacer above the widget but none below, which glues the last row to the input box. */\n private withTrailingSpacer(lines: string[]): string[] {\n if (lines.length === 0) return lines;\n lines.push('');\n return lines;\n }\n\n dispose(): void {\n this.stopProgressTimer();\n if (this.uiCtx) this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.widgetRegistered = false;\n this.tui = undefined;\n this.uiCtx = undefined;\n this.collapsed = false;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"taskOverlay.cjs","names":["visibleTasks","deriveTaskProjection","truncateToWidth","resolveCollapseKey","selectOverlayLayoutFromProjection","getMaxWidgetLines","formatOverlayTaskLine"],"sources":["../../src/tui/taskOverlay.ts"],"sourcesContent":["import type { ExtensionUIContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { type TUI, truncateToWidth } from '@earendil-works/pi-tui';\nimport { COLLAPSE_KEY_OFF, getMaxWidgetLines, resolveCollapseKey } from '../config.ts';\nimport type { DelegationManager } from '../delegation/manager.ts';\nimport type { Task } from '../store/types.ts';\nimport { formatOverlayTaskLine } from './format.ts';\nimport { deriveTaskProjection, selectOverlayLayoutFromProjection, visibleTasks } from './selectors.ts';\n\nconst WIDGET_KEY = 'doom-tasks';\nconst OVERLAY_HEADING = 'Tasks';\nconst PROGRESS_TICK_MS = 1000;\n\nexport interface TaskOverlayOptions {\n getTasks: () => readonly Task[];\n delegation: DelegationManager;\n}\n\n/**\n * Persistent widget listing tasks above the editor.\n *\n * Registered once per UI context and refreshed with `requestRender()`; it\n * auto-hides when there is nothing to show so an idle session keeps a clean\n * prompt.\n */\nexport class TaskOverlay {\n private readonly options: TaskOverlayOptions;\n private uiCtx: ExtensionUIContext | undefined;\n private widgetRegistered = false;\n private tui: TUI | undefined;\n private progressTimer: NodeJS.Timeout | undefined;\n private collapsed = false;\n\n constructor(options: TaskOverlayOptions) {\n this.options = options;\n }\n\n /** Identity-compare so repeated session_start handlers stay idempotent. */\n setUICtx(ctx: ExtensionUIContext): void {\n if (ctx === this.uiCtx) return;\n if (this.uiCtx && this.widgetRegistered) this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.stopProgressTimer();\n this.uiCtx = ctx;\n this.widgetRegistered = false;\n this.tui = undefined;\n }\n\n update(): void {\n if (!this.uiCtx) {\n this.stopProgressTimer();\n return;\n }\n const tasks = visibleTasks(this.options.getTasks());\n\n if (tasks.length === 0) {\n if (this.widgetRegistered) {\n this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.widgetRegistered = false;\n this.tui = undefined;\n }\n this.stopProgressTimer();\n return;\n }\n\n if (this.widgetRegistered) {\n this.syncProgressTimer(tasks);\n this.tui?.requestRender();\n return;\n }\n\n this.uiCtx.setWidget(\n WIDGET_KEY,\n (tui, factoryTheme) => {\n this.tui = tui;\n return {\n render: (width: number) => this.renderWidget(this.uiCtx?.theme ?? factoryTheme, width),\n invalidate: () => {\n // Nothing is cached: the next render reads the live theme and store.\n },\n };\n },\n { placement: 'aboveEditor' },\n );\n this.widgetRegistered = true;\n this.syncProgressTimer(tasks);\n }\n\n private syncProgressTimer(tasks: readonly Task[]): void {\n const hasRunningDelegation = tasks.some(\n (task) => task.status !== 'deleted' && task.delegation?.state === 'running',\n );\n if (this.collapsed || !this.widgetRegistered || !hasRunningDelegation) {\n this.stopProgressTimer();\n return;\n }\n if (this.progressTimer) return;\n this.progressTimer = setInterval(() => this.tui?.requestRender(), PROGRESS_TICK_MS);\n this.progressTimer.unref?.();\n }\n\n private stopProgressTimer(): void {\n if (this.progressTimer) clearInterval(this.progressTimer);\n this.progressTimer = undefined;\n }\n\n toggleCollapse(): void {\n this.collapsed = !this.collapsed;\n this.syncProgressTimer(this.options.getTasks());\n // Forced redraw: collapsing changes the widget height, not just its content.\n this.tui?.requestRender(true);\n }\n\n isRegistered(): boolean {\n return this.widgetRegistered;\n }\n\n private renderWidget(theme: Theme, width: number): string[] {\n const projection = deriveTaskProjection(this.options.getTasks());\n if (projection.visible.length === 0) return [];\n\n const truncate = (line: string): string => truncateToWidth(line, width, '…');\n const headingColor = projection.hasActiveWork ? 'accent' : 'dim';\n const headingIcon = projection.hasActiveWork ? '●' : '○';\n let headingText = `${OVERLAY_HEADING} (${projection.counts.completed}/${projection.counts.total})`;\n if (projection.counts.failed > 0) headingText += ` · ${projection.counts.failed} failed`;\n const heading = truncate(`${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, headingText)}`);\n\n if (this.collapsed) {\n const key = resolveCollapseKey();\n const hint = key === COLLAPSE_KEY_OFF ? 'collapsed' : `${key} to expand`;\n return this.withTrailingSpacer([heading, truncate(`${theme.fg('dim', '└─')} ${theme.fg('dim', hint)}`)]);\n }\n\n const layout = selectOverlayLayoutFromProjection(projection, getMaxWidgetLines() - 1);\n const lines = [heading];\n for (const task of layout.visible) {\n const progress = this.options.delegation.progressFor(task);\n lines.push(\n truncate(`${theme.fg('dim', '├─')} ${formatOverlayTaskLine(task, theme, projection.showIds, progress)}`),\n );\n }\n\n const hidden = layout.hiddenCompleted + layout.truncatedTail;\n if (hidden === 0) {\n const last = lines.length - 1;\n lines[last] = lines[last].replace('├─', '└─');\n return this.withTrailingSpacer(lines);\n }\n\n const parts: string[] = [];\n if (layout.hiddenCompleted > 0) parts.push(`${layout.hiddenCompleted} completed`);\n if (layout.truncatedTail > 0) parts.push(`${layout.truncatedTail} pending`);\n const summary = parts.length > 0 ? `+${hidden} more (${parts.join(', ')})` : `+${hidden} more`;\n lines.push(truncate(`${theme.fg('dim', '└─')} ${theme.fg('dim', summary)}`));\n return this.withTrailingSpacer(lines);\n }\n\n /** Pi adds a spacer above the widget but none below, which glues the last row to the input box. */\n private withTrailingSpacer(lines: string[]): string[] {\n if (lines.length === 0) return lines;\n lines.push('');\n return lines;\n }\n\n dispose(): void {\n this.stopProgressTimer();\n if (this.uiCtx) this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.widgetRegistered = false;\n this.tui = undefined;\n this.uiCtx = undefined;\n this.collapsed = false;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"taskOverlay.d.cts","names":[],"sources":["../../src/tui/taskOverlay.ts"],"mappings":";;;;UAYiB;EACf,yBAAyB;EACzB,YAAY;;;;;;;;;cAUD;mBACM;UACT;UACA;UACA;UACA;UACA;EAEI,YAAA,SAAS;;EAKrB,SAAS,KAAK;EASd;UAwCQ;UAWA;EAKR;EAMA;UAIQ;;UA4CA;EAMR"}
1
+ {"version":3,"file":"taskOverlay.d.cts","names":[],"sources":["../../src/tui/taskOverlay.ts"],"mappings":";;;;UAYiB;EACf,yBAAyB;EACzB,YAAY;;;;;;;;;cAUD;mBACM;UACT;UACA;UACA;UACA;UACA;EAEI,YAAA,SAAS;;EAKrB,SAAS,KAAK;EASd;UAwCQ;UAaA;EAKR;EAOA;UAIQ;;UA0CA;EAMR"}
@@ -1 +1 @@
1
- {"version":3,"file":"taskOverlay.d.mts","names":[],"sources":["../../src/tui/taskOverlay.ts"],"mappings":";;;;UAYiB;EACf,yBAAyB;EACzB,YAAY;;;;;;;;;cAUD;mBACM;UACT;UACA;UACA;UACA;UACA;EAEI,YAAA,SAAS;;EAKrB,SAAS,KAAK;EASd;UAwCQ;UAWA;EAKR;EAMA;UAIQ;;UA4CA;EAMR"}
1
+ {"version":3,"file":"taskOverlay.d.mts","names":[],"sources":["../../src/tui/taskOverlay.ts"],"mappings":";;;;UAYiB;EACf,yBAAyB;EACzB,YAAY;;;;;;;;;cAUD;mBACM;UACT;UACA;UACA;UACA;UACA;EAEI,YAAA,SAAS;;EAKrB,SAAS,KAAK;EASd;UAwCQ;UAaA;EAKR;EAOA;UAIQ;;UA0CA;EAMR"}
@@ -1,2 +1,2 @@
1
- import{getMaxWidgetLines as e,resolveCollapseKey as t}from"../config.mjs";import{formatOverlayTaskLine as n}from"./format.mjs";import{countTasks as r,hasActiveWork as i,selectOverlayLayout as a,shouldShowIds as o,visibleTasks as s}from"./selectors.mjs";import{truncateToWidth as c}from"@earendil-works/pi-tui";const l=`doom-tasks`;var u=class{options;uiCtx;widgetRegistered=!1;tui;progressTimer;collapsed=!1;constructor(e){this.options=e}setUICtx(e){e!==this.uiCtx&&(this.uiCtx&&this.widgetRegistered&&this.uiCtx.setWidget(l,void 0),this.stopProgressTimer(),this.uiCtx=e,this.widgetRegistered=!1,this.tui=void 0)}update(){if(!this.uiCtx){this.stopProgressTimer();return}let e=s(this.options.getTasks());if(e.length===0){this.widgetRegistered&&(this.uiCtx.setWidget(l,void 0),this.widgetRegistered=!1,this.tui=void 0),this.stopProgressTimer();return}if(this.widgetRegistered){this.syncProgressTimer(e),this.tui?.requestRender();return}this.uiCtx.setWidget(l,(e,t)=>(this.tui=e,{render:e=>this.renderWidget(this.uiCtx?.theme??t,e),invalidate:()=>{}}),{placement:`aboveEditor`}),this.widgetRegistered=!0,this.syncProgressTimer(e)}syncProgressTimer(e){if(!e.some(e=>e.delegation?.state===`running`)){this.stopProgressTimer();return}this.progressTimer||(this.progressTimer=setInterval(()=>this.tui?.requestRender(),1e3),this.progressTimer.unref?.())}stopProgressTimer(){this.progressTimer&&clearInterval(this.progressTimer),this.progressTimer=void 0}toggleCollapse(){this.collapsed=!this.collapsed,this.tui?.requestRender(!0)}isRegistered(){return this.widgetRegistered}renderWidget(l,u){let d=s(this.options.getTasks());if(d.length===0)return[];let f=e=>c(e,u,`…`),p=r(d),m=i(d),h=m?`accent`:`dim`,g=m?`●`:`○`,_=`Tasks (${p.completed}/${p.total})`;p.failed>0&&(_+=` · ${p.failed} failed`);let v=f(`${l.fg(h,g)} ${l.fg(h,_)}`);if(this.collapsed){let e=t(),n=e===`off`?`collapsed`:`${e} to expand`;return this.withTrailingSpacer([v,f(`${l.fg(`dim`,`└─`)} ${l.fg(`dim`,n)}`)])}let y=o(d),b=a(d,e()-1),x=[v];for(let e of b.visible){let t=this.options.delegation.progressFor(e);x.push(f(`${l.fg(`dim`,`├─`)} ${n(e,l,y,t)}`))}let S=b.hiddenCompleted+b.truncatedTail;if(S===0){let e=x.length-1;return x[e]=x[e].replace(`├─`,`└─`),this.withTrailingSpacer(x)}let C=[];b.hiddenCompleted>0&&C.push(`${b.hiddenCompleted} completed`),b.truncatedTail>0&&C.push(`${b.truncatedTail} pending`);let w=C.length>0?`+${S} more (${C.join(`, `)})`:`+${S} more`;return x.push(f(`${l.fg(`dim`,`└─`)} ${l.fg(`dim`,w)}`)),this.withTrailingSpacer(x)}withTrailingSpacer(e){return e.length===0||e.push(``),e}dispose(){this.stopProgressTimer(),this.uiCtx&&this.uiCtx.setWidget(l,void 0),this.widgetRegistered=!1,this.tui=void 0,this.uiCtx=void 0,this.collapsed=!1}};export{u as TaskOverlay};
1
+ import{getMaxWidgetLines as e,resolveCollapseKey as t}from"../config.mjs";import{formatOverlayTaskLine as n}from"./format.mjs";import{deriveTaskProjection as r,selectOverlayLayoutFromProjection as i,visibleTasks as a}from"./selectors.mjs";import{truncateToWidth as o}from"@earendil-works/pi-tui";const s=`doom-tasks`;var c=class{options;uiCtx;widgetRegistered=!1;tui;progressTimer;collapsed=!1;constructor(e){this.options=e}setUICtx(e){e!==this.uiCtx&&(this.uiCtx&&this.widgetRegistered&&this.uiCtx.setWidget(s,void 0),this.stopProgressTimer(),this.uiCtx=e,this.widgetRegistered=!1,this.tui=void 0)}update(){if(!this.uiCtx){this.stopProgressTimer();return}let e=a(this.options.getTasks());if(e.length===0){this.widgetRegistered&&(this.uiCtx.setWidget(s,void 0),this.widgetRegistered=!1,this.tui=void 0),this.stopProgressTimer();return}if(this.widgetRegistered){this.syncProgressTimer(e),this.tui?.requestRender();return}this.uiCtx.setWidget(s,(e,t)=>(this.tui=e,{render:e=>this.renderWidget(this.uiCtx?.theme??t,e),invalidate:()=>{}}),{placement:`aboveEditor`}),this.widgetRegistered=!0,this.syncProgressTimer(e)}syncProgressTimer(e){let t=e.some(e=>e.status!==`deleted`&&e.delegation?.state===`running`);if(this.collapsed||!this.widgetRegistered||!t){this.stopProgressTimer();return}this.progressTimer||(this.progressTimer=setInterval(()=>this.tui?.requestRender(),1e3),this.progressTimer.unref?.())}stopProgressTimer(){this.progressTimer&&clearInterval(this.progressTimer),this.progressTimer=void 0}toggleCollapse(){this.collapsed=!this.collapsed,this.syncProgressTimer(this.options.getTasks()),this.tui?.requestRender(!0)}isRegistered(){return this.widgetRegistered}renderWidget(a,s){let c=r(this.options.getTasks());if(c.visible.length===0)return[];let l=e=>o(e,s,`…`),u=c.hasActiveWork?`accent`:`dim`,d=c.hasActiveWork?`●`:`○`,f=`Tasks (${c.counts.completed}/${c.counts.total})`;c.counts.failed>0&&(f+=` · ${c.counts.failed} failed`);let p=l(`${a.fg(u,d)} ${a.fg(u,f)}`);if(this.collapsed){let e=t(),n=e===`off`?`collapsed`:`${e} to expand`;return this.withTrailingSpacer([p,l(`${a.fg(`dim`,`└─`)} ${a.fg(`dim`,n)}`)])}let m=i(c,e()-1),h=[p];for(let e of m.visible){let t=this.options.delegation.progressFor(e);h.push(l(`${a.fg(`dim`,`├─`)} ${n(e,a,c.showIds,t)}`))}let g=m.hiddenCompleted+m.truncatedTail;if(g===0){let e=h.length-1;return h[e]=h[e].replace(`├─`,`└─`),this.withTrailingSpacer(h)}let _=[];m.hiddenCompleted>0&&_.push(`${m.hiddenCompleted} completed`),m.truncatedTail>0&&_.push(`${m.truncatedTail} pending`);let v=_.length>0?`+${g} more (${_.join(`, `)})`:`+${g} more`;return h.push(l(`${a.fg(`dim`,`└─`)} ${a.fg(`dim`,v)}`)),this.withTrailingSpacer(h)}withTrailingSpacer(e){return e.length===0||e.push(``),e}dispose(){this.stopProgressTimer(),this.uiCtx&&this.uiCtx.setWidget(s,void 0),this.widgetRegistered=!1,this.tui=void 0,this.uiCtx=void 0,this.collapsed=!1}};export{c as TaskOverlay};
2
2
  //# sourceMappingURL=taskOverlay.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"taskOverlay.mjs","names":[],"sources":["../../src/tui/taskOverlay.ts"],"sourcesContent":["import type { ExtensionUIContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { type TUI, truncateToWidth } from '@earendil-works/pi-tui';\nimport { COLLAPSE_KEY_OFF, getMaxWidgetLines, resolveCollapseKey } from '../config.ts';\nimport type { DelegationManager } from '../delegation/manager.ts';\nimport type { Task } from '../store/types.ts';\nimport { formatOverlayTaskLine } from './format.ts';\nimport { countTasks, hasActiveWork, selectOverlayLayout, shouldShowIds, visibleTasks } from './selectors.ts';\n\nconst WIDGET_KEY = 'doom-tasks';\nconst OVERLAY_HEADING = 'Tasks';\nconst PROGRESS_TICK_MS = 1000;\n\nexport interface TaskOverlayOptions {\n getTasks: () => readonly Task[];\n delegation: DelegationManager;\n}\n\n/**\n * Persistent widget listing tasks above the editor.\n *\n * Registered once per UI context and refreshed with `requestRender()`; it\n * auto-hides when there is nothing to show so an idle session keeps a clean\n * prompt.\n */\nexport class TaskOverlay {\n private readonly options: TaskOverlayOptions;\n private uiCtx: ExtensionUIContext | undefined;\n private widgetRegistered = false;\n private tui: TUI | undefined;\n private progressTimer: NodeJS.Timeout | undefined;\n private collapsed = false;\n\n constructor(options: TaskOverlayOptions) {\n this.options = options;\n }\n\n /** Identity-compare so repeated session_start handlers stay idempotent. */\n setUICtx(ctx: ExtensionUIContext): void {\n if (ctx === this.uiCtx) return;\n if (this.uiCtx && this.widgetRegistered) this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.stopProgressTimer();\n this.uiCtx = ctx;\n this.widgetRegistered = false;\n this.tui = undefined;\n }\n\n update(): void {\n if (!this.uiCtx) {\n this.stopProgressTimer();\n return;\n }\n const tasks = visibleTasks(this.options.getTasks());\n\n if (tasks.length === 0) {\n if (this.widgetRegistered) {\n this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.widgetRegistered = false;\n this.tui = undefined;\n }\n this.stopProgressTimer();\n return;\n }\n\n if (this.widgetRegistered) {\n this.syncProgressTimer(tasks);\n this.tui?.requestRender();\n return;\n }\n\n this.uiCtx.setWidget(\n WIDGET_KEY,\n (tui, factoryTheme) => {\n this.tui = tui;\n return {\n render: (width: number) => this.renderWidget(this.uiCtx?.theme ?? factoryTheme, width),\n invalidate: () => {\n // Nothing is cached: the next render reads the live theme and store.\n },\n };\n },\n { placement: 'aboveEditor' },\n );\n this.widgetRegistered = true;\n this.syncProgressTimer(tasks);\n }\n\n private syncProgressTimer(tasks: readonly Task[]): void {\n const hasRunningDelegation = tasks.some((task) => task.delegation?.state === 'running');\n if (!hasRunningDelegation) {\n this.stopProgressTimer();\n return;\n }\n if (this.progressTimer) return;\n this.progressTimer = setInterval(() => this.tui?.requestRender(), PROGRESS_TICK_MS);\n this.progressTimer.unref?.();\n }\n\n private stopProgressTimer(): void {\n if (this.progressTimer) clearInterval(this.progressTimer);\n this.progressTimer = undefined;\n }\n\n toggleCollapse(): void {\n this.collapsed = !this.collapsed;\n // Forced redraw: collapsing changes the widget height, not just its content.\n this.tui?.requestRender(true);\n }\n\n isRegistered(): boolean {\n return this.widgetRegistered;\n }\n\n private renderWidget(theme: Theme, width: number): string[] {\n const tasks = visibleTasks(this.options.getTasks());\n if (tasks.length === 0) return [];\n\n const truncate = (line: string): string => truncateToWidth(line, width, '…');\n const counts = countTasks(tasks);\n const active = hasActiveWork(tasks);\n\n const headingColor = active ? 'accent' : 'dim';\n const headingIcon = active ? '●' : '○';\n let headingText = `${OVERLAY_HEADING} (${counts.completed}/${counts.total})`;\n if (counts.failed > 0) headingText += ` · ${counts.failed} failed`;\n const heading = truncate(`${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, headingText)}`);\n\n if (this.collapsed) {\n const key = resolveCollapseKey();\n const hint = key === COLLAPSE_KEY_OFF ? 'collapsed' : `${key} to expand`;\n return this.withTrailingSpacer([heading, truncate(`${theme.fg('dim', '└─')} ${theme.fg('dim', hint)}`)]);\n }\n\n const showIds = shouldShowIds(tasks);\n const layout = selectOverlayLayout(tasks, getMaxWidgetLines() - 1);\n const lines = [heading];\n for (const task of layout.visible) {\n const progress = this.options.delegation.progressFor(task);\n lines.push(truncate(`${theme.fg('dim', '├─')} ${formatOverlayTaskLine(task, theme, showIds, progress)}`));\n }\n\n const hidden = layout.hiddenCompleted + layout.truncatedTail;\n if (hidden === 0) {\n const last = lines.length - 1;\n lines[last] = lines[last].replace('├─', '└─');\n return this.withTrailingSpacer(lines);\n }\n\n const parts: string[] = [];\n if (layout.hiddenCompleted > 0) parts.push(`${layout.hiddenCompleted} completed`);\n if (layout.truncatedTail > 0) parts.push(`${layout.truncatedTail} pending`);\n const summary = parts.length > 0 ? `+${hidden} more (${parts.join(', ')})` : `+${hidden} more`;\n lines.push(truncate(`${theme.fg('dim', '└─')} ${theme.fg('dim', summary)}`));\n return this.withTrailingSpacer(lines);\n }\n\n /** Pi adds a spacer above the widget but none below, which glues the last row to the input box. */\n private withTrailingSpacer(lines: string[]): string[] {\n if (lines.length === 0) return lines;\n lines.push('');\n return lines;\n }\n\n dispose(): void {\n this.stopProgressTimer();\n if (this.uiCtx) this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.widgetRegistered = false;\n this.tui = undefined;\n this.uiCtx = undefined;\n this.collapsed = false;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"taskOverlay.mjs","names":[],"sources":["../../src/tui/taskOverlay.ts"],"sourcesContent":["import type { ExtensionUIContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { type TUI, truncateToWidth } from '@earendil-works/pi-tui';\nimport { COLLAPSE_KEY_OFF, getMaxWidgetLines, resolveCollapseKey } from '../config.ts';\nimport type { DelegationManager } from '../delegation/manager.ts';\nimport type { Task } from '../store/types.ts';\nimport { formatOverlayTaskLine } from './format.ts';\nimport { deriveTaskProjection, selectOverlayLayoutFromProjection, visibleTasks } from './selectors.ts';\n\nconst WIDGET_KEY = 'doom-tasks';\nconst OVERLAY_HEADING = 'Tasks';\nconst PROGRESS_TICK_MS = 1000;\n\nexport interface TaskOverlayOptions {\n getTasks: () => readonly Task[];\n delegation: DelegationManager;\n}\n\n/**\n * Persistent widget listing tasks above the editor.\n *\n * Registered once per UI context and refreshed with `requestRender()`; it\n * auto-hides when there is nothing to show so an idle session keeps a clean\n * prompt.\n */\nexport class TaskOverlay {\n private readonly options: TaskOverlayOptions;\n private uiCtx: ExtensionUIContext | undefined;\n private widgetRegistered = false;\n private tui: TUI | undefined;\n private progressTimer: NodeJS.Timeout | undefined;\n private collapsed = false;\n\n constructor(options: TaskOverlayOptions) {\n this.options = options;\n }\n\n /** Identity-compare so repeated session_start handlers stay idempotent. */\n setUICtx(ctx: ExtensionUIContext): void {\n if (ctx === this.uiCtx) return;\n if (this.uiCtx && this.widgetRegistered) this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.stopProgressTimer();\n this.uiCtx = ctx;\n this.widgetRegistered = false;\n this.tui = undefined;\n }\n\n update(): void {\n if (!this.uiCtx) {\n this.stopProgressTimer();\n return;\n }\n const tasks = visibleTasks(this.options.getTasks());\n\n if (tasks.length === 0) {\n if (this.widgetRegistered) {\n this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.widgetRegistered = false;\n this.tui = undefined;\n }\n this.stopProgressTimer();\n return;\n }\n\n if (this.widgetRegistered) {\n this.syncProgressTimer(tasks);\n this.tui?.requestRender();\n return;\n }\n\n this.uiCtx.setWidget(\n WIDGET_KEY,\n (tui, factoryTheme) => {\n this.tui = tui;\n return {\n render: (width: number) => this.renderWidget(this.uiCtx?.theme ?? factoryTheme, width),\n invalidate: () => {\n // Nothing is cached: the next render reads the live theme and store.\n },\n };\n },\n { placement: 'aboveEditor' },\n );\n this.widgetRegistered = true;\n this.syncProgressTimer(tasks);\n }\n\n private syncProgressTimer(tasks: readonly Task[]): void {\n const hasRunningDelegation = tasks.some(\n (task) => task.status !== 'deleted' && task.delegation?.state === 'running',\n );\n if (this.collapsed || !this.widgetRegistered || !hasRunningDelegation) {\n this.stopProgressTimer();\n return;\n }\n if (this.progressTimer) return;\n this.progressTimer = setInterval(() => this.tui?.requestRender(), PROGRESS_TICK_MS);\n this.progressTimer.unref?.();\n }\n\n private stopProgressTimer(): void {\n if (this.progressTimer) clearInterval(this.progressTimer);\n this.progressTimer = undefined;\n }\n\n toggleCollapse(): void {\n this.collapsed = !this.collapsed;\n this.syncProgressTimer(this.options.getTasks());\n // Forced redraw: collapsing changes the widget height, not just its content.\n this.tui?.requestRender(true);\n }\n\n isRegistered(): boolean {\n return this.widgetRegistered;\n }\n\n private renderWidget(theme: Theme, width: number): string[] {\n const projection = deriveTaskProjection(this.options.getTasks());\n if (projection.visible.length === 0) return [];\n\n const truncate = (line: string): string => truncateToWidth(line, width, '…');\n const headingColor = projection.hasActiveWork ? 'accent' : 'dim';\n const headingIcon = projection.hasActiveWork ? '●' : '○';\n let headingText = `${OVERLAY_HEADING} (${projection.counts.completed}/${projection.counts.total})`;\n if (projection.counts.failed > 0) headingText += ` · ${projection.counts.failed} failed`;\n const heading = truncate(`${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, headingText)}`);\n\n if (this.collapsed) {\n const key = resolveCollapseKey();\n const hint = key === COLLAPSE_KEY_OFF ? 'collapsed' : `${key} to expand`;\n return this.withTrailingSpacer([heading, truncate(`${theme.fg('dim', '└─')} ${theme.fg('dim', hint)}`)]);\n }\n\n const layout = selectOverlayLayoutFromProjection(projection, getMaxWidgetLines() - 1);\n const lines = [heading];\n for (const task of layout.visible) {\n const progress = this.options.delegation.progressFor(task);\n lines.push(\n truncate(`${theme.fg('dim', '├─')} ${formatOverlayTaskLine(task, theme, projection.showIds, progress)}`),\n );\n }\n\n const hidden = layout.hiddenCompleted + layout.truncatedTail;\n if (hidden === 0) {\n const last = lines.length - 1;\n lines[last] = lines[last].replace('├─', '└─');\n return this.withTrailingSpacer(lines);\n }\n\n const parts: string[] = [];\n if (layout.hiddenCompleted > 0) parts.push(`${layout.hiddenCompleted} completed`);\n if (layout.truncatedTail > 0) parts.push(`${layout.truncatedTail} pending`);\n const summary = parts.length > 0 ? `+${hidden} more (${parts.join(', ')})` : `+${hidden} more`;\n lines.push(truncate(`${theme.fg('dim', '└─')} ${theme.fg('dim', summary)}`));\n return this.withTrailingSpacer(lines);\n }\n\n /** Pi adds a spacer above the widget but none below, which glues the last row to the input box. */\n private withTrailingSpacer(lines: string[]): string[] {\n if (lines.length === 0) return lines;\n lines.push('');\n return lines;\n }\n\n dispose(): void {\n this.stopProgressTimer();\n if (this.uiCtx) this.uiCtx.setWidget(WIDGET_KEY, undefined);\n this.widgetRegistered = false;\n this.tui = undefined;\n this.uiCtx = undefined;\n this.collapsed = false;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-task",
3
- "version": "0.0.1-alpha.0",
3
+ "version": "0.0.1-alpha.2",
4
4
  "description": "Pi task tracking extension with file-backed tasks and subagent delegation",
5
5
  "homepage": "https://agimon.ai",
6
6
  "license": "MIT",
@@ -142,9 +142,9 @@
142
142
  },
143
143
  "dependencies": {
144
144
  "typebox": "1.1.38",
145
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.0",
146
- "@agimon-ai/doompi-ui": "0.0.1-alpha.0",
147
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.0"
145
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.2",
146
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.2",
147
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.2"
148
148
  },
149
149
  "devDependencies": {
150
150
  "@earendil-works/pi-coding-agent": "0.84.1",