goal_detail.html 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <pstm-uri>/goals/{goal_id}</pstm-uri>
  5. <pstm-template>main</pstm-template>
  6. <script>
  7. // The goal page (goal 41 T6): the state strip leads — the
  8. // one-glance verdict (status badge, the open-item line, the
  9. // primary lifecycle button) — then the anchor sub-nav, then the
  10. // Act|Consult two-column layout (spry .with-sidebar): the acting
  11. // surfaces (state, blocks/questions, plan review, completion
  12. // review, comments) on the left, the consulting ones (tasks with
  13. // the filter strip, initial brief, involved projects, rules,
  14. // narrative, runs) on the right, stacking Act-then-Consult under
  15. // the framework breakpoint. Below the fold the page keeps its
  16. // established surfaces: the agent's live progress feed, the
  17. // attention blocks (the goal agent's answerable questions/RFCs
  18. // and the completion review), the locked brief, the effective
  19. // rules, the approved plan (revision history behind the
  20. // expander), the supervision narrative (events at full weight,
  21. // comment events as footnotes), the comment thread with
  22. // selection-quoting, and the goal agent's runs. Every long-form
  23. // body (brief, plans, reports, blocks, comments, events) renders
  24. // as markdown via stm-markdown. The plan review collects REMARKS
  25. // through the shared initPlanReviews engine (main.html, plan 16
  26. // R3/arch 15): a per-block queue that renders at the bottom of
  27. // the plan, persists across pulse refreshes (the maps live
  28. // outside statum re-renders), rides the rejection numbered, and
  29. // clears on accept. The tasks card's filter lives client-side in
  30. // window.goalTaskFilter; clicking a strip button re-runs the
  31. // sealed refresh action, and the filter rides the re-render.
  32. if (window.statum) {
  33. (function () {
  34. 'use strict';
  35. window.goalTaskFilter = window.goalTaskFilter || null;
  36. window.goalFilterClass = function (status) {
  37. return window.goalTaskFilter === status ? 'goal-filter active'
  38. : 'goal-filter';
  39. };
  40. window.goalTaskVisible = function (task) {
  41. return window.goalTaskFilter == null
  42. || window.goalTaskFilter === false
  43. || window.goalTaskFilter === ''
  44. || window.goalTaskFilter === task.status;
  45. };
  46. window.goalSetFilter = function (status) {
  47. window.goalTaskFilter = window.goalTaskFilter === status ? null : status;
  48. refreshGoal('filter');
  49. };
  50. function refreshGoal(source) {
  51. var goal = statum.read('goal');
  52. if (!goal || !goal.refresh) return;
  53. statum.act(goal.refresh).catch(function (error) {
  54. console.warn('[goal] live refresh (' + source + ') failed:', error);
  55. });
  56. }
  57. statum.addStateListener('pulse', function () {
  58. refreshGoal('pulse');
  59. });
  60. document.addEventListener('visibilitychange', function () {
  61. if (document.visibilityState === 'visible') refreshGoal('focus');
  62. });
  63. // Goal 41 T6 (bet 2): the anchor sub-nav. Statum re-renders
  64. // replace the DOM, so the listener binds ONCE on document
  65. // and the highlight pass re-runs after every evaluate —
  66. // the same rebound pattern as the filter strip (the
  67. // seals survive because nothing page-scoped is captured).
  68. var SUBNAV = 'goal-subnav';
  69. var SECTIONS = 'goal-section';
  70. function goalHighlightSubnav() {
  71. var nav = document.getElementsByClassName(SUBNAV)[0];
  72. if (!nav) return;
  73. var links = nav.querySelectorAll('a[href^="#"]');
  74. var sections = document.getElementsByClassName(SECTIONS);
  75. if (!links.length || !sections.length) return;
  76. // Two columns share the viewport, so "the last section
  77. // that crossed the line" (the single-column rule) can
  78. // pick a consult-column card while the reader looks at
  79. // an act-column one. The section whose top sits
  80. // CLOSEST to the nav's bottom edge (just below it)
  81. // is the one the reader entered; ties go to the later
  82. // DOM node (the deeper scroll).
  83. var mark = nav.getBoundingClientRect().bottom + 24;
  84. var current = null;
  85. var best = Infinity;
  86. for (var i = 0; i < sections.length; i++) {
  87. var distance = sections[i].getBoundingClientRect().top - mark;
  88. if (distance > 0) continue; // still below the fold line
  89. if (-distance <= best) {
  90. best = -distance;
  91. current = sections[i].id;
  92. }
  93. }
  94. for (var j = 0; j < links.length; j++) {
  95. var active = links[j].getAttribute('href') === '#' + current;
  96. links[j].classList.toggle('active', active);
  97. if (active) links[j].setAttribute('aria-current', 'true');
  98. else links[j].removeAttribute('aria-current');
  99. }
  100. }
  101. document.addEventListener('click', function (event) {
  102. var link = event.target.closest
  103. && event.target.closest('.' + SUBNAV + ' a[href^="#"]');
  104. if (!link) return;
  105. var target = document.getElementById(
  106. link.getAttribute('href').slice(1));
  107. if (!target) return;
  108. event.preventDefault();
  109. target.scrollIntoView();
  110. // scrollIntoView respects the sticky nav only through
  111. // scroll-margin-top (app.css); the highlight lands on
  112. // the raf after the scroll settles.
  113. requestAnimationFrame(goalHighlightSubnav);
  114. });
  115. window.addEventListener('scroll', goalHighlightSubnav,
  116. { passive: true });
  117. window.addEventListener('resize', goalHighlightSubnav,
  118. { passive: true });
  119. statum.afterEvaluate(goalHighlightSubnav);
  120. if (window.initQuoteComments) {
  121. window.initQuoteComments({
  122. matches: function (node) {
  123. // The pending plan body feeds the REMARKS
  124. // popover instead (below) - everything else
  125. // long-form on the goal quote-comments.
  126. return node.closest && node.closest('.goal-body') != null
  127. && node.closest('[data-plan-body]') == null;
  128. },
  129. action: function () {
  130. var goal = statum.read('goal');
  131. return goal && goal.comment;
  132. }
  133. });
  134. }
  135. // The goal's plan-review remarks ride the SHARED
  136. // engine (main.html's initPlanReviews, plan 16 R3/arch
  137. // 15 - the duplicated ~180-line collect-remarks pattern
  138. // now lives once); only the block resolution is page
  139. // state.
  140. // Plan 16 S2: the block for a review container resolves
  141. // from the ACTUAL [data-plan-review] element — the
  142. // pending completion review renders as a LEADING review
  143. // card (goal 41 T6), so goal.plan_pending_block alone
  144. // never resolves it; the slot's three review shapes plus
  145. // every open block are all candidates.
  146. function resolveReviewBlock(id) {
  147. var goal = statum.read('goal');
  148. if (!goal) return null;
  149. var block = null;
  150. (goal.open_blocks || []).forEach(function (candidate) {
  151. if (String(candidate.id) === String(id)) block = candidate;
  152. });
  153. if (!block && goal.completion_pending_block
  154. && String(goal.completion_pending_block.id) === String(id)) {
  155. block = goal.completion_pending_block;
  156. }
  157. if (!block && goal.plan_pending_block
  158. && String(goal.plan_pending_block.id) === String(id)) {
  159. block = goal.plan_pending_block;
  160. }
  161. return block;
  162. }
  163. if (window.initPlanReviews) {
  164. window.initPlanReviews({
  165. resolveBlock: function (id) { return resolveReviewBlock(id); }
  166. });
  167. }
  168. // The brief card's in-place editor (task 12).
  169. if (window.initLongFormEditors) {
  170. window.initLongFormEditors();
  171. }
  172. })();
  173. }
  174. </script>
  175. </head>
  176. <body>
  177. <div class="page goal-page">
  178. <pstm-fragment name="breadcrumb" crumbs="goal.crumbs"/>
  179. <h2 stm-if="goal.exists"><span class="goal-icon">🎯</span> #<span stm-text="goal.id">id</span> —
  180. <span stm-text="goal.title">title</span></h2>
  181. <p stm-if="!goal.exists" class="text-muted">Goal not found.</p>
  182. <div stm-if="goal.exists">
  183. <!-- GOAL 41 T6 (quick win 7): the one-line STATE STRIP —
  184. the verdict without parsing the Status card: status
  185. badge, the open-item line ("2 questions waiting · plan
  186. revision 3 pending"), the PRIMARY lifecycle button
  187. (the one matching the current state; the full ladder
  188. keeps living on the Status card). Nothing else — the
  189. strip is one glance. -->
  190. <div class="goal-state-strip" data-goal-state-strip>
  191. <span stm-attribute.class="'badge badge-' + goal.status"
  192. stm-text="goal.display">status</span>
  193. <span class="goal-state-verdict">
  194. <span stm-if="goal.open_blocks.length == 0
  195. && (goal.plan_pending_block == null
  196. || goal.plan_pending_block == false)
  197. && !goal.completion_pending"
  198. class="text-muted">No open items — nothing waits on
  199. you.</span>
  200. <span stm-if="goal.plan_pending_block != null
  201. && goal.plan_pending_block != false">
  202. Plan revision #<span stm-text="goal.plan_pending_block.id">n</span>
  203. pending</span>
  204. <span stm-if="goal.completion_pending">Completion review
  205. pending</span>
  206. <span stm-if="goal.open_blocks.length == 1"
  207. stm-text="'1 ' + (goal.open_blocks[0].block_type == 'question'
  208. ? 'question' : goal.open_blocks[0].block_type)
  209. + ' waiting'">1 waiting</span>
  210. <span stm-if="goal.open_blocks.length > 1"
  211. stm-text="goal.open_blocks.length + ' items waiting'">n waiting</span>
  212. </span>
  213. <span class="goal-state-actions">
  214. <button type="button" class="btn-primary btn-sm"
  215. stm-if="goal.start != null" stm-action="goal.start">Start
  216. goal</button>
  217. <button type="button" class="btn-primary btn-sm"
  218. stm-if="goal.resume != null" stm-action="goal.resume">Resume</button>
  219. <button type="button" class="btn-primary btn-sm"
  220. stm-if="goal.retry_run != null" stm-action="goal.retry_run">Retry
  221. supervision</button>
  222. <button type="button" class="btn-primary btn-sm"
  223. stm-if="goal.pause != null" stm-action="goal.pause">Pause</button>
  224. <button type="button" class="btn-primary btn-sm"
  225. stm-if="goal.requeue_run != null" stm-action="goal.requeue_run"
  226. stm-confirm="Requeue the agent's run? The daemon restores its workspace context on the next dispatch.">Requeue
  227. to resume</button>
  228. <button type="button" class="btn-primary btn-sm"
  229. stm-if="goal.force_wake != null" stm-action="goal.force_wake"
  230. stm-confirm="Force-woke this goal's parked agent? It resumes immediately on its saddle - one wake per press.">Force
  231. wake</button>
  232. </span>
  233. </div>
  234. <!-- GOAL 41 T6 (bet 2): the anchor sub-nav — Status ·
  235. Blocks · Plan · Tasks · Brief · Narrative · Comments.
  236. Sticky under the topbar (app.css reads its height);
  237. the highlight listener lives in the page script
  238. (rebound through statum.afterEvaluate). -->
  239. <nav class="goal-subnav" aria-label="Sections" data-goal-subnav>
  240. <a href="#goal-sec-status">Status</a>
  241. <a href="#goal-sec-blocks">Blocks</a>
  242. <a href="#goal-sec-plan">Plan</a>
  243. <a href="#goal-sec-tasks">Tasks</a>
  244. <a href="#goal-sec-brief">Brief</a>
  245. <a href="#goal-sec-narrative">Narrative</a>
  246. <a href="#goal-sec-comments">Comments</a>
  247. </nav>
  248. <!-- GOAL 41 T6 (bet 1): Act|Consult — spry's
  249. .with-sidebar grid with the proportions flipped (the
  250. acting column is the wide one; app.css owns the
  251. columns + the sub-55rem stack). ACT carries the
  252. attention order the page already had: pending
  253. reviews lead (actions surface), then state, blocks,
  254. plan, comments. CONSULT carries the reference
  255. surfaces the reader jumps to. -->
  256. <div class="goal-columns with-sidebar" data-goal-columns>
  257. <div class="stack" data-goal-act>
  258. <!-- GOAL 41 T6 ("actions surface"): while a plan review is
  259. pending, the review surface LEADS the acting column
  260. (stm-if ordering by state) — the reader answers before
  261. anything else. The plan-history card below renders
  262. only when nothing is pending. -->
  263. <section class="card plan-review-card goal-section"
  264. stm-if="goal.plan_pending_block != null && goal.plan_pending_block != false"
  265. id="goal-sec-plan-review">
  266. <h3>Plan review</h3>
  267. <div class="plan-review" data-plan-review
  268. stm-attribute.data-block-id="goal.plan_pending_block.id">
  269. <h4>Plan submitted for review</h4>
  270. <div class="md rfc-body goal-body" data-plan-body
  271. stm-markdown="goal.plan_pending_block.body">revision</div>
  272. <div class="actions">
  273. <form stm-action="goal.plan_pending_block.answer_action" class="cluster">
  274. <input type="hidden" name="verdict" value="accept">
  275. <button type="submit" class="btn-primary" data-plan-accept>Approve plan</button>
  276. </form>
  277. <button type="button" data-plan-toggle>Reject with feedback</button>
  278. </div>
  279. <div data-plan-feedback hidden>
  280. <p class="text-muted">Highlight plan text to attach a remark to it — the queue
  281. rides the rejection numbered, then clears. Approving ignores the queue.</p>
  282. <ul class="plan-comments" data-plan-remarks></ul>
  283. <textarea name="feedback" rows="2"
  284. placeholder="Additional feedback (optional)"></textarea>
  285. <button type="button" class="btn-primary" data-plan-submit>Submit feedback</button>
  286. </div>
  287. </div>
  288. </section>
  289. <!-- GOAL 41 T6 ("actions surface"): the pending completion
  290. review leads the acting column the same way — hoisted
  291. out of the blocks loop (which skips review rows) and
  292. keyed on the slot's completion_pending_block. -->
  293. <section class="card plan-review-card goal-section"
  294. stm-if="goal.completion_pending_block != null && goal.completion_pending_block != false"
  295. id="goal-sec-completion-review">
  296. <h3>Completion review</h3>
  297. <div class="plan-review" data-plan-review
  298. stm-attribute.data-block-id="goal.completion_pending_block.id">
  299. <pstm-fragment name="goal-branches"/>
  300. <pstm-fragment name="goal-compare"/>
  301. <div class="md rfc-body goal-body" data-plan-body
  302. stm-markdown="goal.completion_pending_block.body">report</div>
  303. <div class="actions">
  304. <form stm-action="goal.completion_pending_block.answer_action" class="cluster">
  305. <input type="hidden" name="verdict" value="accept">
  306. <button type="submit" class="btn-primary" data-plan-accept>Accept goal</button>
  307. </form>
  308. <button type="button" data-plan-toggle>Reject with feedback</button>
  309. </div>
  310. <div data-plan-feedback hidden>
  311. <p class="text-muted">Highlight report text to attach a remark to it — the queue
  312. rides the rejection numbered, then clears. Accepting ignores the queue.</p>
  313. <ul class="plan-comments" data-plan-remarks></ul>
  314. <textarea name="feedback" rows="2"
  315. placeholder="Additional feedback (optional)"></textarea>
  316. <button type="button" class="btn-primary" data-plan-submit>Submit feedback</button>
  317. </div>
  318. </div>
  319. </section>
  320. <!-- Status head (first in the ACT column while no review
  321. awaits): lifecycle badge, branch pair with the
  322. daemon-reported push-state chips, priority,
  323. timestamps, the assigned/parking agent saddle and
  324. the FULL lifecycle ladder — with run control parity
  325. (plan 14 B1): Retry supervision for a failed goal,
  326. Requeue-to-resume for a run stale/parked on a dead
  327. saddle, Cancel. NO notify-me toggle — goals notify
  328. unconditionally by design. Yeehaw stays on task
  329. rows only. The strip above carries the one-glance
  330. verdict; this card keeps the detail. -->
  331. <section class="card goal-head goal-section" id="goal-sec-status">
  332. <div class="card-head">
  333. <h3>Status</h3>
  334. </div>
  335. <p><span stm-attribute.class="'badge badge-' + goal.status" stm-text="goal.display">status</span>
  336. · goal branch <code stm-text="goal.branch_name">branch</code>
  337. <span class="text-muted">from</span> <code stm-text="goal.base_branch">base</code>
  338. <a stm-if="goal.compare_url.length > 0" stm-attribute.href="goal.compare_url"
  339. target="_blank" rel="noopener">· compare
  340. changes</a>
  341. · priority <strong stm-text="goal.priority">3</strong>
  342. <span stm-if="goal.agent_saddle.length > 0"> · agent saddle
  343. <strong stm-text="goal.agent_saddle">saddle</strong></span></p>
  344. <pstm-fragment name="goal-branches"
  345. note="&quot;(the daemon's /status report)&quot;"/>
  346. <p class="text-muted">Project:
  347. <a stm-attribute.href="'/projects/' + goal.project_id" stm-text="goal.project_name">project</a>
  348. · Created <span stm-text="goal.created_display">when</span>
  349. <span stm-if="goal.started_display.length > 0"> · Started
  350. <span stm-text="goal.started_display">when</span></span>
  351. <span stm-if="goal.completed_display.length > 0"> · Finished
  352. <span stm-text="goal.completed_display">when</span></span></p>
  353. <p>
  354. <button type="button" class="btn-primary" stm-if="goal.start != null"
  355. stm-action="goal.start">Start goal</button>
  356. <button type="button" stm-if="goal.pause != null"
  357. stm-action="goal.pause">Pause</button>
  358. <button type="button" stm-if="goal.resume != null"
  359. stm-action="goal.resume">Resume</button>
  360. <button type="button" class="btn-primary" stm-if="goal.retry_run != null"
  361. stm-action="goal.retry_run">Retry supervision</button>
  362. <button type="button" stm-if="goal.requeue_run != null"
  363. stm-action="goal.requeue_run"
  364. stm-confirm="Requeue the agent's run? The daemon restores its workspace context on the next dispatch.">Requeue to resume</button>
  365. <!-- Goal 4 task 1: Force wake — the parked-agent
  366. recovery for the no-pending-wake stall. -->
  367. <button type="button" stm-if="goal.force_wake != null"
  368. stm-action="goal.force_wake"
  369. stm-confirm="Force-woke this goal's parked agent? It resumes immediately on its saddle - one wake per press.">Force wake</button>
  370. <button type="button" class="danger" stm-if="goal.cancel != null"
  371. stm-action="goal.cancel"
  372. stm-confirm="Cancel this goal? Its tasks are cancelled with it.">Cancel goal</button>
  373. </p>
  374. <p class="text-muted" stm-if="goal.status == 'draft'">Starting locks the brief and
  375. hands the goal to its agent (an orchestrate-mode run task, priority 3).</p>
  376. <p class="text-muted" stm-if="goal.status == 'paused'">Paused — supervision frozen;
  377. task events queue and coalesce into the resume.</p>
  378. <!-- Plan 15 C4: the ONE muted activity line (the
  379. Current-activity card is gone) — what the run is
  380. doing right now, folded into the status head. -->
  381. <p class="text-muted" stm-if="goal.current_activity.length > 0"
  382. stm-text="goal.current_activity">activity</p>
  383. <p class="text-muted" stm-if="goal.saddle_health.length > 0"
  384. stm-text="goal.saddle_health">health</p>
  385. <!-- S5: the goal's aggregate LLM usage across its tasks
  386. (the run plus the workers) — empty renders nothing. -->
  387. <p class="text-muted" stm-if="goal.usage_line.length > 0"
  388. stm-text="goal.usage_line">LLM: 1,234 calls · 45.2M in / 1.1M out tok · 3h 12m</p>
  389. </section>
  390. <!-- The agent's live progress feed (task-page pattern):
  391. the newest line leads, its history behind the
  392. expander. Plan 15 C4: a CARD again (the goalui
  393. restyle regression — every section rides the same
  394. card rhythm). -->
  395. <section class="card">
  396. <h3>Agent progress</h3>
  397. <pstm-fragment name="progress-feed" entries="goal.progress"/>
  398. <p stm-if="goal.progress.length == 0" class="text-muted">No progress reports yet — the goal
  399. agent reports via <code>saddle progress-update</code>.</p>
  400. </section>
  401. <!-- ATTENTION (plan 14 B5 order; plan 15 C4): the goal
  402. agent's OPEN blocks lead — questions and RFCs with
  403. full markdown bodies and inline answer forms — and
  404. the historic answered ones fold behind the
  405. answered-blocks expander (the task page's pattern);
  406. the completion review follows when one awaits. -->
  407. <section class="card goal-section"
  408. stm-if="goal.open_blocks.length > 0 || goal.answered_blocks.length > 0"
  409. id="goal-sec-blocks">
  410. <h3>Goal agent blocks</h3>
  411. <div stm-for-block-in="goal.open_blocks" class="goal-block">
  412. <!-- A pending completion review renders in the
  413. LEADING completion-review card above (goal 41
  414. T6 "actions surface"); the loop shows every
  415. other open block — questions and RFCs with
  416. full markdown bodies and inline answer forms. -->
  417. <p stm-if="!block.is_completion_review"><span stm-attribute.class="'badge badge-block-' + block.block_type"
  418. stm-text="block.block_type">kind</span>
  419. <strong stm-text="block.question">question</strong>
  420. <span class="text-muted" stm-text="block.created_display">when</span></p>
  421. <div stm-if="!block.is_completion_review">
  422. <div stm-if="block.show_body && block.body.length > 0"
  423. class="md rfc-body goal-body" stm-markdown="block.body">body</div>
  424. <pstm-fragment name="block-answer"/>
  425. <pstm-fragment name="block-reply"/>
  426. </div>
  427. </div>
  428. <details class="history-expander" stm-if="goal.answered_blocks.length > 0">
  429. <summary>Answered blocks (<span stm-text="goal.answered_blocks.length">0</span>)</summary>
  430. <div stm-for-block-in="goal.answered_blocks" class="goal-block">
  431. <p><span stm-attribute.class="'badge badge-block-' + block.block_type"
  432. stm-text="block.block_type">kind</span>
  433. <strong stm-text="block.question">question</strong>
  434. <span class="text-muted" stm-text="block.created_display">when</span></p>
  435. <div stm-if="block.show_body && block.body.length > 0"
  436. class="md rfc-body goal-body" stm-markdown="block.body">body</div>
  437. <blockquote stm-if="block.answered" class="term-comment-quote">
  438. <span stm-text="block.answered_by">who</span>:
  439. <span stm-text="block.answer">answer</span></blockquote>
  440. </div>
  441. </details>
  442. </section>
  443. <!-- Completion review card: the TERMINAL summary — shown
  444. once the goal is complete/failed, with the report and
  445. the compare link. While PENDING, the review lives in
  446. the attention blocks above (the rich review form). -->
  447. <section class="card goal-section" stm-if="goal.terminal && goal.completion_report.length > 0">
  448. <h3>Completion review</h3>
  449. <pstm-fragment name="goal-branches"/>
  450. <pstm-fragment name="goal-compare"/>
  451. <div class="md rfc-body goal-body" stm-markdown="goal.completion_report">report</div>
  452. </section>
  453. <!-- The plan history (plan 15 C4): the approved plan and
  454. the revision history (approved + rejected) sit behind
  455. a collapsed expander — the goal page leads with what
  456. needs a human, not with settled history. While a plan
  457. review is open the LEADING plan-review card above
  458. carries it (this card waits below). -->
  459. <section class="card goal-section" id="goal-sec-plan">
  460. <h3>Plan</h3>
  461. <details class="history-expander">
  462. <summary>Approved plan &amp; revisions (<span
  463. stm-text="goal.plan_revisions.length">0</span>)</summary>
  464. <div stm-if="goal.plan.length > 0" class="md rfc-body goal-body" data-plan-body
  465. stm-markdown="goal.plan">plan</div>
  466. <p stm-if="goal.plan.length == 0" class="text-muted">No approved plan yet.</p>
  467. <div stm-for-revision-in="goal.plan_revisions" class="plan-revision">
  468. <p><span stm-attribute.class="'badge ' + (revision.approved ? 'badge-done' : (revision.answered ? 'badge-failed' : 'badge-plan'))"
  469. stm-text="revision.approved ? 'approved' : (revision.answered ? 'rejected' : 'pending')">verdict</span>
  470. <span class="text-muted">Revision #<span stm-text="revision.id">n</span> &mdash; <span stm-text="revision.created_display">when</span></span></p>
  471. <details>
  472. <summary>Submitted markdown</summary>
  473. <div class="md rfc-body" stm-markdown="revision.body">body</div>
  474. </details>
  475. <blockquote stm-if="revision.answered" class="term-comment-quote">
  476. <span stm-text="revision.answered_by">who</span>:
  477. <span stm-text="revision.answer">answer</span></blockquote>
  478. </div>
  479. </details>
  480. </section>
  481. <!-- Comments with selection-quoting (markdown bodies).
  482. Plan 15 C2: the thread IS the goal-run task's own
  483. task_comments — the agent's pushes and the human's
  484. composer land on the same rows; harness-authored
  485. bookkeeping renders as distinct muted system rows,
  486. never agent comment rows. The composer opens with
  487. the run (a draft goal has no thread to inject into). -->
  488. <section class="card goal-section" id="goal-sec-comments">
  489. <h3>Comments</h3>
  490. <!-- Plan 15 C4: timeless-comment footnote styling —
  491. the BODY leads; the author and time read as a
  492. muted footnote (status belongs in progress
  493. updates). System rows keep their labelled style. -->
  494. <div stm-for-comment-in="goal.comments"
  495. stm-attribute.class="'comment' + (comment.from_agent ? ' agent' : '') + (comment.is_system ? ' system' : '')">
  496. <div class="md" stm-markdown="comment.body">body</div>
  497. <blockquote stm-if="comment.quote.length > 0" stm-text="comment.quote">quote</blockquote>
  498. <p class="comment-meta">
  499. <strong stm-if="!comment.is_system" stm-text="comment.author">author</strong>
  500. <span stm-if="comment.is_system" class="system-label">system</span>
  501. <span class="text-muted" stm-text="comment.created_display">when</span>
  502. </p>
  503. </div>
  504. <p stm-if="goal.comments.length == 0" class="text-muted">No comments yet.</p>
  505. <p stm-if="goal.run_task_id == 0" class="text-muted">The goal agent has not started
  506. yet — comments open with its run.</p>
  507. <!-- Goal 8 task 4: the composer stays OPEN on terminal
  508. goals — a comment there re-opens the goal and
  509. re-offers its agent's run with the comment riding
  510. the resume (cancelled goals stay closed: their
  511. composer keeps the run gate alone). -->
  512. <form stm-if="goal.run_task_id > 0" stm-action="goal.comment">
  513. <div class="field"><label for="goal-comment-body">Add comment</label>
  514. <textarea id="goal-comment-body" name="body" rows="3"
  515. placeholder="A timeless note for a future reader — status belongs in progress updates (markdown)"></textarea></div>
  516. <p stm-if="goal.terminal && goal.status != 'cancelled'" class="text-muted">
  517. This goal is terminal — commenting re-opens it: the agent resumes
  518. with your comment and submits a fresh completion report.</p>
  519. <button type="submit" class="btn-primary">Comment</button>
  520. </form>
  521. <dialog id="quote-comment-dialog">
  522. <h3>Comment on selected text</h3>
  523. <blockquote id="quote-text" class="term-comment-quote"></blockquote>
  524. <form stm-action="goal.comment">
  525. <input type="hidden" name="quote">
  526. <input type="hidden" name="return_to">
  527. <textarea name="body" rows="4" placeholder="Your comment…"
  528. style="width:100%; margin:0.5em 0;"></textarea>
  529. <button type="submit" class="btn-primary">Send comment</button>
  530. <button type="button"
  531. onclick="document.getElementById('quote-comment-dialog').close()">Cancel</button>
  532. </form>
  533. </dialog>
  534. </section>
  535. </div><!-- /data-goal-act -->
  536. <div class="stack" data-goal-consult>
  537. <!-- Task status strip + the goal's task list (plan 14 B5:
  538. tasks above the brief; capability badges per row —
  539. no editing here; yeehaw on rows only). -->
  540. <section class="card goal-section" id="goal-sec-tasks">
  541. <h3>Tasks</h3>
  542. <p class="goal-strip">
  543. <button type="button" stm-attribute.class="window.goalFilterClass('draft')"
  544. onclick="window.goalSetFilter('draft')"
  545. stm-text="'draft ' + goal.draft_tasks">draft</button>
  546. <button type="button" stm-attribute.class="window.goalFilterClass('queued')"
  547. onclick="window.goalSetFilter('queued')"
  548. stm-text="'queued ' + goal.queued_tasks">queued</button>
  549. <button type="button" stm-attribute.class="window.goalFilterClass('working')"
  550. onclick="window.goalSetFilter('working')"
  551. stm-text="'running ' + goal.running_tasks">running</button>
  552. <button type="button" stm-attribute.class="window.goalFilterClass('done')"
  553. onclick="window.goalSetFilter('done')"
  554. stm-text="'done ' + goal.done_tasks">done</button>
  555. <button type="button" stm-attribute.class="window.goalFilterClass('failed')"
  556. onclick="window.goalSetFilter('failed')"
  557. stm-text="'failed ' + goal.failed_tasks">failed</button>
  558. <span class="text-muted">— click a status to filter</span>
  559. </p>
  560. <table>
  561. <thead><tr><th>#</th><th>Title</th><th>Status</th><th>Mode</th><th>Requires</th><th>Created by</th></tr></thead>
  562. <tbody>
  563. <tr stm-for-goal_task-in="goal.goal_tasks"
  564. stm-if="window.goalTaskVisible(goal_task)">
  565. <td stm-text="goal_task.id">id</td>
  566. <td><a stm-attribute.href="'/tasks/' + goal_task.id"
  567. stm-text="goal_task.title">title</a></td>
  568. <td><span stm-attribute.class="'badge badge-' + goal_task.status"
  569. stm-text="goal_task.display">status</span>
  570. </td>
  571. <td><span stm-attribute.class="'badge badge-mode-' + goal_task.mode"
  572. stm-text="goal_task.mode">mode</span></td>
  573. <td><span stm-for-capability-in="goal_task.capabilities">
  574. <span class="badge badge-capability"
  575. stm-text="capability">tag</span> </span></td>
  576. <td class="text-muted" stm-text="goal_task.creator">who</td>
  577. </tr>
  578. <tr stm-if="goal.goal_tasks.length == 0">
  579. <td colspan="6" class="text-muted">No tasks yet — the goal agent fans
  580. them out once the plan is approved.</td></tr>
  581. </tbody>
  582. </table>
  583. </section>
  584. <!-- The locked brief. Task 12: DRAFT goals swap the
  585. rendered markdown for an in-place editor
  586. (initLongFormEditors in main.html) carrying title,
  587. brief and priority — a draft goal has no other edit
  588. surface (this also unblocks the draft priority, which
  589. nothing else could change). Starting locks the brief:
  590. the editor's stm-if wrapper disappears with the
  591. update action and the card stays the plain read view. -->
  592. <section class="card goal-section" id="goal-sec-brief" data-long-form data-field="goal"
  593. data-lf-open="0">
  594. <h3>Initial brief
  595. <button type="button" class="btn-ghost" stm-if="goal.update != null"
  596. data-lf-toggle>Edit</button>
  597. <span class="text-muted" stm-if="goal.started_display.length > 0">(locked)</span></h3>
  598. <div data-lf-view>
  599. <div class="md rfc-body goal-body" data-brief-body stm-markdown="goal.brief">brief</div>
  600. </div>
  601. <div stm-if="goal.update != null">
  602. <form data-lf-edit hidden stm-action="goal.update">
  603. <div class="field"><label>Title</label>
  604. <input type="text" name="title" data-lf-src="goal.title"></div>
  605. <div class="field"><label>Brief (markdown)</label>
  606. <textarea name="brief" rows="8"
  607. data-lf-src="goal.brief">brief</textarea></div>
  608. <div class="field"><label>Priority (1–9)</label>
  609. <input type="number" name="priority" min="1" max="9"
  610. data-lf-src="goal.priority"></div>
  611. <button type="submit">Save</button>
  612. <button type="button" data-lf-toggle>Cancel</button>
  613. </form>
  614. </div>
  615. </section>
  616. <!-- Involved projects. -->
  617. <section class="card">
  618. <h3>Involved projects</h3>
  619. <p stm-if="goal.links.length > 0"><span stm-for-link-in="goal.links">
  620. <span stm-attribute.class="'badge badge-' + link.relationship"
  621. stm-text="link.name + ' (' + link.relationship + ')'">link</span>
  622. <button type="button" class="btn-ghost" stm-action="link.remove"
  623. stm-confirm="Remove this link?">&times;</button>
  624. <input type="hidden" name="link_id" stm-attribute.value="link.id">
  625. </span></p>
  626. <p stm-if="goal.links.length == 0" class="text-muted">Only the primary project.</p>
  627. <details stm-if="!goal.terminal">
  628. <summary>Link a project</summary>
  629. <form class="cluster" stm-action="goal.add_link">
  630. <select name="linked_project_id" required>
  631. <option stm-for-project-in="goal.project_options" stm-attribute.value="project.id"
  632. stm-text="project.name">project</option>
  633. </select>
  634. <select name="relationship">
  635. <option value="co-developed">co-developed</option>
  636. <option value="reference">reference</option>
  637. </select>
  638. <button type="submit">Link</button>
  639. </form>
  640. <form class="cluster" stm-action="goal.apply_stack"
  641. stm-if="goal.stack_options.length > 0" style="margin-top: 0.5em;">
  642. <label for="goal-stack">Apply stack</label>
  643. <select id="goal-stack" name="stack_id" required>
  644. <option stm-for-stack-in="goal.stack_options" stm-attribute.value="stack.id"
  645. stm-text="stack.name + ' (' + stack.member_count + ')'">stack</option>
  646. </select>
  647. <button type="submit">Apply</button>
  648. </form>
  649. </details>
  650. </section>
  651. <!-- Plan 14 B5: the goal's effective rules — they govern
  652. the agent and every worker it fans out. -->
  653. <section class="card">
  654. <h3>Rules</h3>
  655. <p class="text-muted">The effective set dispatched with this goal's work — task rules,
  656. the primary project's, then co-developed projects' in link order.</p>
  657. <div stm-for-rule-in="goal.rules" class="rule-card">
  658. <h4 stm-text="rule.name">name</h4>
  659. <div class="md" stm-markdown="rule.body">body</div>
  660. </div>
  661. <p stm-if="goal.rules.length == 0" class="text-muted">No rules apply to this goal.</p>
  662. </section>
  663. <!-- The supervision narrative (plan 14 B6): state events
  664. at full weight, comment events as .footnote — the
  665. feed reads as events-with-occasional-footnotes. -->
  666. <section class="card goal-section" id="goal-sec-narrative">
  667. <h3>Supervision narrative</h3>
  668. <p stm-if="goal.events.length == 0" class="text-muted">Nothing yet — start the goal.</p>
  669. <div stm-for-event-in="goal.events"
  670. stm-attribute.class="'goal-event' + (event.kind == 'comment' ? ' footnote' : '')">
  671. <span class="text-muted" stm-text="event.at_display">when</span>
  672. <span stm-attribute.class="'badge badge-goal-' + event.kind"
  673. stm-text="event.kind.replace('-', ' ')">kind</span>
  674. <!-- Comment events render as footnotes (plan 13 B4
  675. + 14 B6): the whole row is muted and smaller,
  676. the body-first text leading. -->
  677. <div class="md goal-event-text" stm-markdown="event.text">text</div>
  678. <a stm-if="event.task_id > 0"
  679. stm-attribute.href="'/tasks/' + event.task_id">task</a>
  680. </div>
  681. </section>
  682. <!-- Goal agent runs: every round with its host saddle; the
  683. event log links open in a new tab. -->
  684. <section class="card" stm-if="goal.runs.length > 0">
  685. <h3>Goal agent runs</h3>
  686. <p><a stm-attribute.href="'/tasks/' + goal.run_task_id + '/log'"
  687. target="_blank" rel="noopener">Event log</a>
  688. <span class="text-muted">— run task
  689. #<span stm-text="goal.run_task_id">id</span></span></p>
  690. <table>
  691. <thead><tr><th>Run</th><th>Saddle</th><th>Started</th><th>Ended</th><th>Outcome</th></tr></thead>
  692. <tbody>
  693. <tr stm-for-run-in="goal.runs">
  694. <td><a stm-attribute.href="run.terminal_url" target="_blank" rel="noopener"
  695. stm-text="'session ' + run.id">run</a></td>
  696. <td class="text-muted" stm-text="run.saddle_name">saddle</td>
  697. <td class="text-muted" stm-text="run.started_display">when</td>
  698. <td class="text-muted" stm-text="run.ended_display">when</td>
  699. <td><span stm-if="run.open" class="badge badge-working">open</span>
  700. <span stm-if="!run.open" stm-text="run.outcome">outcome</span></td>
  701. </tr>
  702. </tbody>
  703. </table>
  704. </section>
  705. </div><!-- /data-goal-consult -->
  706. </div><!-- /goal-columns -->
  707. </div>
  708. </div>
  709. </body>
  710. </html>