瀏覽代碼

B5: goal page order, effective-Rules section, per-task capability badges

The page reads top-down as supervision: status head (with progress and
current activity in the status region) → ATTENTION blocks (the goal
agent's answerable questions/RFCs, then the completion review) → tasks
(strip + rows, worker links, yeehaw on rows) → locked brief → involved
projects → rules → plan (with the remarks queue) → narrative →
comments → runs. New Rules section lists the goal's EFFECTIVE rules —
the set a dispatch ships for the run and every worker: run-task rules,
the primary project's, then the co-developed goal links' in link order
(the wire contract's stable order). Task rows carry their
required-capability tags as badges (read-only — editing stays on the
task page).
clanker 1 月之前
父節點
當前提交
35dfc4dc55
共有 4 個文件被更改,包括 239 次插入 和 130 次删除
  1. 2 1
      src/actions/GoalActions.vala
  2. 61 3
      src/entrypoints/GoalDetailEntrypoint.vala
  3. 13 0
      src/pages/app.css
  4. 163 126
      src/pages/goal_detail.html

+ 2 - 1
src/actions/GoalActions.vala

@@ -393,6 +393,7 @@ namespace ClankerSaddle {
         private ProjectService project_service = inject<ProjectService>();
         private SaddleService saddle_service = inject<SaddleService>();
         private SaddlePollState poll_state = inject<SaddlePollState>();
+        private RuleService rule_service = inject<RuleService>();
         private InvercargillSql.Orm.OrmSession db = Inversion.inject<InvercargillSql.Orm.OrmSession>();
         private UserService users = inject<UserService>();
 
@@ -409,7 +410,7 @@ namespace ClankerSaddle {
 
             var state = yield GoalState.build(goal_id, goal_service, task_service,
                 project_service, db, action_registry, state_service,
-                saddle_service, poll_state);
+                saddle_service, poll_state, rule_service);
             return directives().set(state);
         }
     }

+ 61 - 3
src/entrypoints/GoalDetailEntrypoint.vala

@@ -77,6 +77,20 @@ namespace ClankerSaddle {
         public string creator { get; set; default = "human"; }
         public bool yeehaw { get; set; default = false; }
         public string updated_display { get; set; default = ""; }
+        /** The task's required capability tags (plan 14 B5 row badges). */
+        public string[] capabilities { get; set; default = {}; }
+    }
+
+    /**
+     * One effective rule of the goal (plan 14 B5): the rules that
+     * govern the goal agent and every worker under it — the run
+     * task's own set, the primary project's, and the co-developed
+     * links' in link order.
+     */
+    public class GoalRuleView : Object {
+        public int64 id { get; set; }
+        public string name { get; set; default = ""; }
+        public string body { get; set; default = ""; }
     }
 
     /** One goal-agent run (a session of the goal-run task). */
@@ -184,7 +198,8 @@ namespace ClankerSaddle {
                 TaskService tasks, ProjectService projects,
                 InvercargillSql.Orm.OrmSession db, ActionRegistry? actions,
                 StateService state_service,
-                SaddleService? saddles = null, SaddlePollState? polls = null) throws GLib.Error {
+                SaddleService? saddles = null, SaddlePollState? polls = null,
+                RuleService? rules = null) throws GLib.Error {
             var pub = new GoalPublic();
             var links = new Vector<GoalLinkView>();
             var blocks = new Vector<GoalBlockView>();
@@ -194,6 +209,7 @@ namespace ClankerSaddle {
             var runs = new Vector<GoalRunView>();
             var push_branches = new Vector<GoalPushBranchView>();
             var progress = new Vector<GoalProgressView>();
+            var effective_rules = new Vector<GoalRuleView>();
 
             GoalBlockView? plan_pending = null;
             GoalBlockView? completion_pending = null;
@@ -224,6 +240,24 @@ namespace ClankerSaddle {
                         });
                     }
                 }
+                // Plan 14 B5: the goal's effective rules — the set a
+                // dispatch ships for the run (and every worker):
+                // run-task rules, the primary project's, then the
+                // co-developed goal links' in link order.
+                if (rules != null) {
+                    var scope_task = new TaskEntity() {
+                        id = pub.run_task_id,
+                        project_id = ((!)goal).project_id
+                    };
+                    foreach (var rule in yield ((!)rules).effective_rules_for(
+                            scope_task, projects, ((!)goal).id)) {
+                        effective_rules.add(new GoalRuleView() {
+                            id = rule.id,
+                            name = rule.name,
+                            body = rule.body
+                        });
+                    }
+                }
             }
 
             if (actions != null && goal != null) {
@@ -239,7 +273,12 @@ namespace ClankerSaddle {
                     dict["options"] = StateJson.strings(
                         ((GoalBlockView) item).options ?? new string[0]);
                 });
-            pub_root["goal_tasks"] = StateJson.list<GoalTaskView>(goal_tasks.to_immutable_buffer());
+            pub_root["goal_tasks"] = StateJson.list_rich<GoalTaskView>(
+                goal_tasks.to_immutable_buffer(),
+                (item, dict) => {
+                    dict["capabilities"] = StateJson.strings(
+                        ((GoalTaskView) item).capabilities ?? new string[0]);
+                });
             pub_root["events"] = StateJson.list<GoalEventView>(events.to_immutable_buffer());
             pub_root["comments"] = StateJson.list<GoalCommentView>(comments.to_immutable_buffer());
             pub_root["runs"] = StateJson.list<GoalRunView>(runs.to_immutable_buffer());
@@ -249,6 +288,8 @@ namespace ClankerSaddle {
                 push_branches.to_immutable_buffer());
             pub_root["plan_revisions"] = StateJson.list<GoalBlockView>(
                 plan_revisions.to_immutable_buffer());
+            pub_root["rules"] = StateJson.list<GoalRuleView>(
+                effective_rules.to_immutable_buffer());
             pub_root["project_options"] = StateJson.list<GoalProjectOptionView>(
                 project_options.to_immutable_buffer());
             pub_root["plan_pending_block"] = plan_pending != null
@@ -393,6 +434,22 @@ namespace ClankerSaddle {
                     creator = task.creator,
                     updated_display = format_stamp(task.completed_at ?? task.created_at)
                 };
+                // Plan 14 B5: the row's required-capability badges —
+                // read straight off the task_capabilities links (no
+                // editing here).
+                var task_caps = new Vector<string>();
+                foreach (var link in yield db.query<TaskCapability>()
+                        .where(expr("task_id == $0", elem<int64?>(task.id)))
+                        .order_by(expr("id"))
+                        .materialise_async()) {
+                    var capability = yield db.query<Capability>()
+                        .where(expr("id == $0", elem<int64?>(link.capability_id)))
+                        .first_async();
+                    if (capability != null) {
+                        task_caps.add(((!)capability).name);
+                    }
+                }
+                view.capabilities = task_caps.to_array();
                 var display = display_map.get_or_default(task.id.to_string());
                 view.display = display != null ? ((!)display).display : task.status;
                 view.yeehaw = display != null && ((!)display).yeehaw;
@@ -767,6 +824,7 @@ namespace ClankerSaddle {
         private ProjectService project_service = Inversion.inject<ProjectService>();
         private SaddleService saddle_service = Inversion.inject<SaddleService>();
         private SaddlePollState poll_state = Inversion.inject<SaddlePollState>();
+        private RuleService rule_service = Inversion.inject<RuleService>();
         private InvercargillSql.Orm.OrmSession db = Inversion.inject<InvercargillSql.Orm.OrmSession>();
         private UserService users = Inversion.inject<UserService>();
 
@@ -779,7 +837,7 @@ namespace ClankerSaddle {
             int64 goal_id = int64.parse(request.route("goal_id") ?? "0");
             var state = yield GoalState.build(goal_id, goal_service, task_service,
                 project_service, db, action_registry, state_service,
-                saddle_service, poll_state);
+                saddle_service, poll_state, rule_service);
             return directives().set(state).subscribe_global(StatePulse.SLOT_TYPE);
         }
     }

+ 13 - 0
src/pages/app.css

@@ -342,6 +342,19 @@ nav .nav-goal { font-weight: 600; }
     margin: 0.35rem 0;
     padding-left: 0.6rem;
 }
+
+/* Plan 14 B5: the goal's Rules section rows and the task rows'
+   required-capability badges. */
+.rule-card {
+    border: 1px solid var(--line-soft);
+    border-radius: 6px;
+    margin: var(--grid-1) 0;
+    padding: var(--grid-1) var(--grid-2);
+}
+
+.rule-card > h4 { margin: 0 0 var(--grid-1); }
+
+.badge-capability { background: #ddf4ff; color: #0969da; }
 .plan-revision { margin: 0.5rem 0; }
 
 /* Dashboard timeline event kinds (plan 10 phase 3) */

+ 163 - 126
src/pages/goal_detail.html

@@ -5,17 +5,25 @@
     <pstm-template>main</pstm-template>
 <script>
     // The goal page: supervision surface of one goal — status head
-    // with lifecycle actions, branch/push-state chips, the agent's
-    // live progress feed and current activity, the locked brief, the
-    // approved plan (revision history behind the expander), the
-    // task status strip (its buttons filter the task list), the
-    // supervision narrative, the comment thread with
-    // selection-quoting, the completion review and the goal agent's
-    // runs. Every long-form body (brief, plans, reports, blocks,
-    // comments, events) renders as markdown via stm-markdown. The
-    // status strip's filter lives client-side in
-    // window.goalTaskFilter; clicking a strip button re-runs the
-    // sealed refresh action, and the filter rides the re-render.
+    // with lifecycle actions (plus run-control parity: retry
+    // supervision, requeue-to-resume), branch/push-state chips, the
+    // agent's live progress feed and current activity, the ATTENTION
+    // blocks (the goal agent's answerable questions/RFCs and the
+    // completion review), the task status strip with per-row
+    // capability badges, the locked brief, the goal's effective
+    // rules, the approved plan (revision history behind the
+    // expander), the supervision narrative (events at full weight,
+    // comment events as footnotes), the comment thread with
+    // selection-quoting, and the goal agent's runs. Every long-form
+    // body (brief, plans, reports, blocks, comments, events) renders
+    // as markdown via stm-markdown. The plan review collects REMARKS
+    // (plan 14 B2): "Add remark" appends to a per-block queue that
+    // renders at the bottom of the plan, persists across pulse
+    // refreshes (the maps live outside statum re-renders), rides the
+    // rejection numbered, and clears on accept. The status strip's
+    // filter lives client-side in window.goalTaskFilter; clicking a
+    // strip button re-runs the sealed refresh action, and the filter
+    // rides the re-render.
     if (window.statum) {
         (function () {
             'use strict';
@@ -277,9 +285,15 @@
         <p stm-if="!goal.exists" class="muted">Goal not found.</p>
 
         <div stm-if="goal.exists">
-            <!-- Status head: lifecycle badge, branch pair with the
-                 daemon-reported push-state chips, priority, the
-                 assigned agent saddle and the lifecycle actions. -->
+            <!-- Status head (first, always): lifecycle badge, branch
+                 pair with the daemon-reported push-state chips,
+                 priority, timestamps, the assigned/parking agent
+                 saddle and the lifecycle actions — with full run
+                 control parity (plan 14 B1): Retry supervision for a
+                 failed goal, Requeue-to-resume for a run stale/parked
+                 on a dead saddle, Cancel. NO notify-me toggle — goals
+                 notify unconditionally by design. Yeehaw stays on
+                 task rows only. -->
             <section class="card goal-head">
                 <div class="card-head">
                     <h3>Status</h3>
@@ -326,9 +340,9 @@
                     task events queue and coalesce into the resume.</p>
             </section>
 
-            <!-- The agent's live progress feed (task-page pattern):
-                 the newest line leads, its history behind the
-                 expander. -->
+            <!-- The agent's live progress feed (task-page pattern),
+                 inside the status region: the newest line leads, its
+                 history behind the expander. -->
             <h3>Agent progress</h3>
             <div stm-if="goal.progress.length > 0">
                 <p class="progress-latest" stm-text="goal.progress[0].summary">latest</p>
@@ -346,9 +360,9 @@
             <p stm-if="goal.progress.length == 0" class="muted">No progress reports yet — the goal
                 agent reports via <code>saddle progress-update</code>.</p>
 
-            <!-- Current activity (task-page parity): what the agent's
-                 open round is doing right now, plus its saddle's
-                 health. -->
+            <!-- Current activity (task-page parity), inside the
+                 status region: what the agent's open round is doing
+                 right now, plus its saddle's health. -->
             <section class="card goal-activity" stm-if="goal.run_task_id > 0">
                 <h3>Current activity</h3>
                 <p stm-if="goal.current_command.length > 0">
@@ -361,6 +375,116 @@
                    stm-text="goal.saddle_health">health</p>
             </section>
 
+            <!-- ATTENTION (plan 14 B5 order): the goal agent's
+                 answerable blocks lead — questions and RFCs with full
+                 markdown bodies and inline answer forms, then the
+                 completion review when one awaits. -->
+            <section class="card" stm-if="goal.blocks.length > 0">
+                <h3>Goal agent blocks</h3>
+                <div stm-for-block-in="goal.blocks" class="goal-block">
+                    <div stm-if="!block.is_plan_review && !block.is_completion_review">
+                        <p><span stm-attribute.class="'badge badge-block-' + block.block_type"
+                                 stm-text="block.block_type">kind</span>
+                            <strong stm-text="block.question">question</strong>
+                            <span class="muted" stm-text="block.created_display">when</span></p>
+                        <div stm-if="block.show_body && block.body.length > 0"
+                             class="md rfc-body goal-body" stm-markdown="block.body">body</div>
+                        <blockquote stm-if="block.answered" class="term-comment-quote">
+                            <span stm-text="block.answered_by">who</span>:
+                            <span stm-text="block.answer">answer</span></blockquote>
+                        <form stm-if="!block.answered" stm-action="block.answer_action">
+                            <div stm-if="block.options.length > 0" class="form-row">
+                                <label>Options</label>
+                                <select name="option">
+                                    <option stm-for-option-in="block.options" stm-attribute.value="option"
+                                             stm-text="option">opt</option>
+                                </select></div>
+                            <div class="form-row">
+                                <textarea name="answer" rows="2"
+                                          placeholder="Reply (or pick an option)"></textarea></div>
+                            <button type="submit" class="primary">Answer</button>
+                        </form>
+                    </div>
+                </div>
+            </section>
+
+            <!-- Completion review: the report, compare link (an
+                 answerable attention block). -->
+            <section class="card" stm-if="goal.completion_pending">
+                <h3>Completion review</h3>
+                <p stm-if="goal.push_branches.length > 0">Goal branches:
+                    <span stm-for-branch-in="goal.push_branches">
+                        <code stm-text="branch.branch">b</code>
+                        <span stm-attribute.class="branch.pushed ? 'badge badge-done' : 'badge badge-failed'"
+                              stm-text="branch.pushed ? 'pushed' : 'unpushed'">state</span>
+                    </span></p>
+                <p><a stm-if="goal.compare_url.length > 0" stm-attribute.href="goal.compare_url"
+                      target="_blank" rel="noopener">Compare changes</a></p>
+                <div class="md rfc-body goal-body" stm-markdown="goal.completion_report">report</div>
+                <form stm-action="goal.completion_pending_block.answer_action" class="inline">
+                    <input type="hidden" name="answer"
+                           value="Review approved. Continue with the task.">
+                    <button type="submit" class="approve">Accept goal</button>
+                </form>
+                <details>
+                    <summary>Request changes</summary>
+                    <form stm-action="goal.completion_pending_block.answer_action">
+                        <div class="form-row">
+                            <textarea name="answer" rows="4" required
+                                      placeholder="What remains to be done?"></textarea></div>
+                        <button type="submit" class="reject">Send feedback</button>
+                    </form>
+                </details>
+            </section>
+
+            <!-- Task status strip + the goal's task list (plan 14 B5:
+                 tasks above the brief; capability badges per row —
+                 no editing here; yeehaw on rows only). -->
+            <section class="card">
+                <h3>Tasks</h3>
+                <p class="goal-strip">
+                    <button type="button" stm-attribute.class="window.goalFilterClass('draft')"
+                            onclick="window.goalSetFilter('draft')"
+                            stm-text="'draft ' + goal.draft_tasks">draft</button>
+                    <button type="button" stm-attribute.class="window.goalFilterClass('queued')"
+                            onclick="window.goalSetFilter('queued')"
+                            stm-text="'queued ' + goal.queued_tasks">queued</button>
+                    <button type="button" stm-attribute.class="window.goalFilterClass('working')"
+                            onclick="window.goalSetFilter('working')"
+                            stm-text="'running ' + goal.running_tasks">running</button>
+                    <button type="button" stm-attribute.class="window.goalFilterClass('done')"
+                            onclick="window.goalSetFilter('done')"
+                            stm-text="'done ' + goal.done_tasks">done</button>
+                    <button type="button" stm-attribute.class="window.goalFilterClass('failed')"
+                            onclick="window.goalSetFilter('failed')"
+                            stm-text="'failed ' + goal.failed_tasks">failed</button>
+                    <span class="muted">— click a status to filter</span>
+                </p>
+                <table>
+                    <thead><tr><th>#</th><th>Title</th><th>Status</th><th>Mode</th><th>Requires</th><th>Created by</th></tr></thead>
+                    <tbody>
+                        <tr stm-for-goal_task-in="goal.goal_tasks"
+                            stm-if="window.goalTaskVisible(goal_task)">
+                            <td stm-text="goal_task.id">id</td>
+                            <td><a stm-attribute.href="'/tasks/' + goal_task.id"
+                                   stm-text="goal_task.title">title</a></td>
+                            <td><span stm-attribute.class="'badge badge-' + goal_task.status"
+                                      stm-text="goal_task.display">status</span>
+                                <span class="badge badge-yeehaw" stm-if="goal_task.yeehaw">🤠</span></td>
+                            <td><span stm-attribute.class="'badge badge-mode-' + goal_task.mode"
+                                      stm-text="goal_task.mode">mode</span></td>
+                            <td><span stm-for-capability-in="goal_task.capabilities">
+                                <span class="badge badge-capability"
+                                      stm-text="capability">tag</span> </span></td>
+                            <td class="muted" stm-text="goal_task.creator">who</td>
+                        </tr>
+                        <tr stm-if="goal.goal_tasks.length == 0">
+                            <td colspan="6" class="muted">No tasks yet — the goal agent fans
+                                them out once the plan is approved.</td></tr>
+                    </tbody>
+                </table>
+            </section>
+
             <!-- The locked brief. -->
             <section class="card">
                 <h3>Initial brief
@@ -394,9 +518,23 @@
                 </details>
             </section>
 
-            <!-- The plan: the approved markdown; the pending review;
-                 the revision history (approved + rejected) behind the
-                 expander, task-page pattern. -->
+            <!-- Plan 14 B5: the goal's effective rules — they govern
+                 the agent and every worker it fans out. -->
+            <section class="card">
+                <h3>Rules</h3>
+                <p class="muted">The effective set dispatched with this goal's work — task rules,
+                    the primary project's, then co-developed projects' in link order.</p>
+                <div stm-for-rule-in="goal.rules" class="rule-card">
+                    <h4 stm-text="rule.name">name</h4>
+                    <div class="md" stm-markdown="rule.body">body</div>
+                </div>
+                <p stm-if="goal.rules.length == 0" class="muted">No rules apply to this goal.</p>
+            </section>
+
+            <!-- The plan: the approved markdown; the pending review
+                 with the remarks queue (plan 14 B2); the revision
+                 history (approved + rejected) behind the expander,
+                 task-page pattern. -->
             <section class="card">
                 <h3>Plan</h3>
                 <div stm-if="goal.plan.length > 0" class="md rfc-body goal-body" data-plan-body
@@ -404,7 +542,8 @@
                 <p stm-if="goal.plan.length == 0 && goal.plan_pending_block == null"
                    class="muted">No approved plan yet.</p>
 
-                <div stm-if="goal.plan_pending_block != null && goal.plan_pending_block != false" class="plan-review" data-plan-review
+                <div stm-if="goal.plan_pending_block != null && goal.plan_pending_block != false"
+                     class="plan-review" data-plan-review
                      stm-attribute.data-block-id="goal.plan_pending_block.id">
                     <h4>Plan submitted for review</h4>
                     <div class="md rfc-body goal-body" data-plan-body
@@ -445,77 +584,6 @@
                 </details>
             </section>
 
-            <!-- Completion review: the report, compare link. -->
-            <section class="card" stm-if="goal.completion_pending">
-                <h3>Completion review</h3>
-                <p stm-if="goal.push_branches.length > 0">Goal branches:
-                    <span stm-for-branch-in="goal.push_branches">
-                        <code stm-text="branch.branch">b</code>
-                        <span stm-attribute.class="branch.pushed ? 'badge badge-done' : 'badge badge-failed'"
-                              stm-text="branch.pushed ? 'pushed' : 'unpushed'">state</span>
-                    </span></p>
-                <p><a stm-if="goal.compare_url.length > 0" stm-attribute.href="goal.compare_url"
-                      target="_blank" rel="noopener">Compare changes</a></p>
-                <div class="md rfc-body goal-body" stm-markdown="goal.completion_report">report</div>
-                <form stm-action="goal.completion_pending_block.answer_action" class="inline">
-                    <input type="hidden" name="answer"
-                           value="Review approved. Continue with the task.">
-                    <button type="submit" class="approve">Accept goal</button>
-                </form>
-                <details>
-                    <summary>Request changes</summary>
-                    <form stm-action="goal.completion_pending_block.answer_action">
-                        <div class="form-row">
-                            <textarea name="answer" rows="4" required
-                                      placeholder="What remains to be done?"></textarea></div>
-                        <button type="submit" class="reject">Send feedback</button>
-                    </form>
-                </details>
-            </section>
-
-            <!-- Task status strip + the goal's task list. -->
-            <section class="card">
-                <h3>Tasks</h3>
-                <p class="goal-strip">
-                    <button type="button" stm-attribute.class="window.goalFilterClass('draft')"
-                            onclick="window.goalSetFilter('draft')"
-                            stm-text="'draft ' + goal.draft_tasks">draft</button>
-                    <button type="button" stm-attribute.class="window.goalFilterClass('queued')"
-                            onclick="window.goalSetFilter('queued')"
-                            stm-text="'queued ' + goal.queued_tasks">queued</button>
-                    <button type="button" stm-attribute.class="window.goalFilterClass('working')"
-                            onclick="window.goalSetFilter('working')"
-                            stm-text="'running ' + goal.running_tasks">running</button>
-                    <button type="button" stm-attribute.class="window.goalFilterClass('done')"
-                            onclick="window.goalSetFilter('done')"
-                            stm-text="'done ' + goal.done_tasks">done</button>
-                    <button type="button" stm-attribute.class="window.goalFilterClass('failed')"
-                            onclick="window.goalSetFilter('failed')"
-                            stm-text="'failed ' + goal.failed_tasks">failed</button>
-                    <span class="muted">— click a status to filter</span>
-                </p>
-                <table>
-                    <thead><tr><th>#</th><th>Title</th><th>Status</th><th>Mode</th><th>Created by</th></tr></thead>
-                    <tbody>
-                        <tr stm-for-goal_task-in="goal.goal_tasks"
-                            stm-if="window.goalTaskVisible(goal_task)">
-                            <td stm-text="goal_task.id">id</td>
-                            <td><a stm-attribute.href="'/tasks/' + goal_task.id"
-                                   stm-text="goal_task.title">title</a></td>
-                            <td><span stm-attribute.class="'badge badge-' + goal_task.status"
-                                      stm-text="goal_task.display">status</span>
-                                <span class="badge badge-yeehaw" stm-if="goal_task.yeehaw">🤠</span></td>
-                            <td><span stm-attribute.class="'badge badge-mode-' + goal_task.mode"
-                                      stm-text="goal_task.mode">mode</span></td>
-                            <td class="muted" stm-text="goal_task.creator">who</td>
-                        </tr>
-                        <tr stm-if="goal.goal_tasks.length == 0">
-                            <td colspan="5" class="muted">No tasks yet — the goal agent fans
-                                them out once the plan is approved.</td></tr>
-                    </tbody>
-                </table>
-            </section>
-
             <!-- The supervision narrative: one chronological feed of
                  the goal agent's life (markdown bodies — reports land
                  here whole). -->
@@ -567,37 +635,6 @@
                 </dialog>
             </section>
 
-            <!-- The goal agent's blocks (questions/RFCs) with full
-                 markdown bodies and their inline answer forms. -->
-            <section class="card" stm-if="goal.blocks.length > 0">
-                <h3>Goal agent blocks</h3>
-                <div stm-for-block-in="goal.blocks" class="goal-block">
-                    <div stm-if="!block.is_plan_review && !block.is_completion_review">
-                        <p><span stm-attribute.class="'badge badge-block-' + block.block_type"
-                                 stm-text="block.block_type">kind</span>
-                            <strong stm-text="block.question">question</strong>
-                            <span class="muted" stm-text="block.created_display">when</span></p>
-                        <div stm-if="block.show_body && block.body.length > 0"
-                             class="md rfc-body goal-body" stm-markdown="block.body">body</div>
-                        <blockquote stm-if="block.answered" class="term-comment-quote">
-                            <span stm-text="block.answered_by">who</span>:
-                            <span stm-text="block.answer">answer</span></blockquote>
-                        <form stm-if="!block.answered" stm-action="block.answer_action">
-                            <div stm-if="block.options.length > 0" class="form-row">
-                                <label>Options</label>
-                                <select name="option">
-                                    <option stm-for-option-in="block.options" stm-attribute.value="option"
-                                             stm-text="option">opt</option>
-                                </select></div>
-                            <div class="form-row">
-                                <textarea name="answer" rows="2"
-                                          placeholder="Reply (or pick an option)"></textarea></div>
-                            <button type="submit" class="primary">Answer</button>
-                        </form>
-                    </div>
-                </div>
-            </section>
-
             <!-- Goal agent runs: every round with its host saddle; the
                  terminal links open in a new tab (the session picker
                  lives on the terminal page). -->