| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <pstm-uri>/tasks</pstm-uri>
- <pstm-template>main</pstm-template>
- <script>
- // The task queue. Plan 12: the filters (status / project / goal)
- // run CLIENT-SIDE - the Statum entrypoint fetch never carries the
- // page's own query string to the server, so the status chips and
- // the selects filter the rendered rows directly (window.taskFilters
- // + taskRowVisible) and survive re-renders; the create form gains
- // the mode select (with descriptions) and the auto-enqueue-after
- // dropdown (the global human-task list; goal tasks are the goal
- // agent's to sequence).
- //
- // Goal 41 T4: the status select became faceted chips with counts
- // (the goal-detail strip pattern) and the default view is OPEN
- // WORK - queued+assigned+working+blocked+suspended - instead of
- // "all", so months of done rows no longer bury the queue (ux-notes
- // tasks A+B). taskFilters.status holds the chip state:
- // null -> every status (the "All" chip)
- // 'open' -> the default open-work set
- // '<status>' -> one exact status
- if (window.statum) {
- (function () {
- 'use strict';
- window.TASK_OPEN_STATUSES = ['queued', 'assigned', 'working',
- 'blocked', 'suspended'];
- window.taskFilters = window.taskFilters || {};
- window.taskStatusCount = function (status) {
- var tasks = statum.read('tasks');
- var rows = (tasks && tasks.tasks) || [];
- var count = 0;
- for (var i = 0; i < rows.length; i++) {
- if (status === 'open') {
- if (window.TASK_OPEN_STATUSES.indexOf(rows[i].status) !== -1) {
- count += 1;
- }
- } else if (status === 'all') {
- count += 1;
- } else if (rows[i].status === status) {
- count += 1;
- }
- }
- return count;
- };
- window.taskRowVisible = function (row) {
- var filters = window.taskFilters || {};
- if (filters.status && filters.status !== 'all') {
- if (filters.status === 'open') {
- if (window.TASK_OPEN_STATUSES.indexOf(row.status) === -1) {
- return false;
- }
- } else if (row.status !== filters.status) {
- return false;
- }
- }
- if (filters.project && filters.project > 0
- && String(row.project_id) !== String(filters.project)) {
- return false;
- }
- if (filters.goal) {
- if (filters.goal === 'standalone' && row.goal_id > 0) {
- return false;
- }
- if (filters.goal !== 'standalone'
- && String(row.goal_id) !== String(filters.goal)) {
- return false;
- }
- }
- return true;
- };
- window.setTaskFilter = function (name, value) {
- window.taskFilters[name] = value;
- var tasks = statum.read('tasks');
- if (tasks && tasks.refresh) {
- statum.act(tasks.refresh).catch(function () { });
- } else {
- // No sealed refresh on this build: force a render
- // cycle through the pulse listener if present.
- document.body.setAttribute('data-filter-bump', String(Date.now()));
- }
- };
- window.taskChipClass = function (status) {
- var current = (window.taskFilters || {}).status;
- if (current == null || current === '') current = 'open';
- return current === status ? 'goal-filter active' : 'goal-filter';
- };
- window.setTaskStatusFilter = function (status) {
- var current = (window.taskFilters || {}).status;
- if (current == null || current === '') current = 'open';
- if (current === status) return; // one chip is always on
- window.setTaskFilter('status', status);
- };
- // Default to open work until a chip says otherwise.
- if (window.taskFilters.status == null) {
- window.taskFilters.status = 'open';
- }
- })();
- }
- </script>
- </head>
- <body>
- <div class="stack">
- <div class="cluster cluster-between">
- <h1>Tasks</h1>
- <button type="button" class="btn-primary"
- onclick="window.openAppDialog('task-create-dialog')">+ New task</button>
- </div>
- <!-- Goal 41 T4: faceted status chips with counts (the
- goal-strip pattern ported to the queue). One chip is
- always active; the default is "Open" - queued, assigned,
- working, blocked, suspended - so the queue leads and
- done/failed stay one click away with their counts
- visible. -->
- <p class="goal-strip">
- <button type="button" stm-attribute.class="window.taskChipClass('open')"
- onclick="window.setTaskStatusFilter('open')"
- stm-text="'Open ' + window.taskStatusCount('open')">open</button>
- <button type="button" stm-attribute.class="window.taskChipClass('draft')"
- onclick="window.setTaskStatusFilter('draft')"
- stm-text="'draft ' + window.taskStatusCount('draft')">draft</button>
- <button type="button" stm-attribute.class="window.taskChipClass('queued')"
- onclick="window.setTaskStatusFilter('queued')"
- stm-text="'queued ' + window.taskStatusCount('queued')">queued</button>
- <button type="button" stm-attribute.class="window.taskChipClass('assigned')"
- onclick="window.setTaskStatusFilter('assigned')"
- stm-text="'assigned ' + window.taskStatusCount('assigned')">assigned</button>
- <button type="button" stm-attribute.class="window.taskChipClass('working')"
- onclick="window.setTaskStatusFilter('working')"
- stm-text="'working ' + window.taskStatusCount('working')">working</button>
- <button type="button" stm-attribute.class="window.taskChipClass('blocked')"
- onclick="window.setTaskStatusFilter('blocked')"
- stm-text="'blocked ' + window.taskStatusCount('blocked')">blocked</button>
- <button type="button" stm-attribute.class="window.taskChipClass('suspended')"
- onclick="window.setTaskStatusFilter('suspended')"
- stm-text="'suspended ' + window.taskStatusCount('suspended')">suspended</button>
- <button type="button" stm-attribute.class="window.taskChipClass('done')"
- onclick="window.setTaskStatusFilter('done')"
- stm-text="'done ' + window.taskStatusCount('done')">done</button>
- <button type="button" stm-attribute.class="window.taskChipClass('failed')"
- onclick="window.setTaskStatusFilter('failed')"
- stm-text="'failed ' + window.taskStatusCount('failed')">failed</button>
- <button type="button" stm-attribute.class="window.taskChipClass('all')"
- onclick="window.setTaskStatusFilter('all')"
- stm-text="'all ' + window.taskStatusCount('all')">all</button>
- </p>
- <form class="cluster" onsubmit="return false">
- <label for="filter-project">Project</label>
- <select id="filter-project" onchange="window.setTaskFilter('project', this.value)">
- <option value="0">all</option>
- <option stm-for-project-in="tasks.projects" stm-attribute.value="project.id"
- stm-text="project.name">project</option>
- </select>
- <label for="filter-goal">Goal</label>
- <select id="filter-goal" onchange="window.setTaskFilter('goal', this.value)">
- <option value="all">all</option>
- <option value="standalone">standalone only</option>
- <option stm-for-goal-in="tasks.goal_options" stm-attribute.value="goal.id"
- stm-text="goal.title">goal</option>
- </select>
- </form>
- <div class="table-wrap">
- <table class="table-hover">
- <thead>
- <tr><th>#</th><th>Title</th><th>Status</th><th>Mode</th><th>Goal</th>
- <th>Priority</th><th>Project</th><th>Capabilities</th></tr>
- </thead>
- <tbody>
- <tr stm-for-task-in="tasks.tasks" stm-if="window.taskRowVisible(task)">
- <td stm-text="task.id">id</td>
- <td>
- <a stm-attribute.href="'/tasks/' + task.id" stm-text="task.title">title</a>
- </td>
- <td><span stm-attribute.class="'badge badge-' + task.status" stm-text="task.display">status</span></td>
- <td><span stm-attribute.class="'badge badge-mode-' + task.mode" stm-text="task.mode">mode</span></td>
- <td stm-if="task.goal_id > 0">
- <a stm-attribute.href="'/goals/' + task.goal_id" stm-text="task.goal_title">goal</a>
- <span class="text-muted" stm-if="task.creator == 'goal'">(agent)</span>
- </td>
- <td stm-if="task.goal_id == 0" class="text-muted">—</td>
- <td stm-text="task.priority">5</td>
- <td stm-text="task.project_name">project</td>
- <td><span stm-if="task.capabilities.length > 0"
- stm-text="task.capabilities.join(', ')">caps</span>
- <span class="text-muted" stm-if="task.capabilities.length == 0">—</span></td>
- </tr>
- <tr stm-if="tasks.tasks.length == 0"><td colspan="8" class="text-muted">No tasks.</td></tr>
- </tbody>
-
- </table>
- </div>
- </div>
- <!-- Goal 41 T4: the create form moved from a collapsed expander
- into a Spry-styled native <dialog> opened by the "+ New
- task" button (ux-notes quick win 4). The form itself is
- unchanged - same fields, same stm-action - and the shared
- openAppDialog helper closes it on the action's notify. -->
- <dialog id="task-create-dialog">
- <div class="dialog-titlebar">
- <p class="dialog-title">New task</p>
- <button type="button" class="dialog-close" aria-label="Close"
- onclick="window.closeAppDialog('task-create-dialog')">×</button>
- </div>
- <form stm-action="tasks.create">
- <div class="dialog-body form-rows">
- <div class="field"><label for="task-project">Project</label>
- <!-- Goal 8 task 4: "No project" posts project_id 0 —
- a STANDALONE task with its own ephemeral
- workspace; the completion report is the
- deliverable. -->
- <select id="task-project" name="project_id">
- <option value="0" selected>No project (standalone)</option>
- <option stm-for-project-in="tasks.projects" stm-attribute.value="project.id"
- stm-text="project.name">project</option>
- </select></div>
- <div class="field"><label for="task-title">Title</label>
- <input type="text" id="task-title" name="title" required></div>
- <div class="field"><label for="task-mode">Mode</label>
- <select id="task-mode" name="mode">
- <option value="implement" selected>implement — build the change on a task branch</option>
- <option value="investigate">investigate — explore and report, no code changes</option>
- <option value="review">review — read and critique existing work</option>
- <option value="test">test — write and run tests</option>
- </select></div>
- <div class="field"><label for="task-description">Mandate</label>
- <textarea id="task-description" name="description" rows="4"
- placeholder="What the agent must accomplish (the mandate)"></textarea></div>
- <div class="field"><label for="task-branch">Task branch name
- <span class="text-muted">(optional — empty lets the system name the branch; goal worker tasks auto-name task/<goal-id>-<task-id>)</span></label>
- <input type="text" id="task-branch" name="task_branch"
- placeholder="task/<id>-<slug>"></div>
- <div class="field"><label for="task-branch-from">Branch from
- <span class="text-muted">(default: the project's default branch)</span></label>
- <input type="text" id="task-branch-from" name="branch_from"
- placeholder="main"></div>
- <div class="field"><label for="task-priority">Priority</label>
- <select id="task-priority" name="priority">
- <option value="1">1 — highest</option>
- <option value="2">2</option>
- <option value="3">3</option>
- <option value="4">4</option>
- <option value="5" selected>5 — default</option>
- <option value="6">6</option>
- <option value="7">7</option>
- <option value="8">8</option>
- <option value="9">9 — lowest</option>
- </select></div>
- <div class="field"><label for="task-enqueue-after">Auto-enqueue after
- <span class="text-muted">(optional — enqueues itself when the predecessor is done)</span></label>
- <select id="task-enqueue-after" name="enqueue_after_task_id">
- <option value="">none</option>
- <option stm-for-option-in="tasks.enqueue_after_options"
- stm-attribute.value="option.id" stm-text="option.name">task</option>
- </select></div>
- <div class="field" stm-if="tasks.capability_options.length > 0">
- <label for="task-capabilities">Capabilities
- <span class="text-muted">(the saddle fleet must carry them until assigned)</span></label>
- <select id="task-capabilities" name="required_capabilities" multiple
- size="3">
- <option stm-for-capability-in="tasks.capability_options"
- stm-attribute.value="capability.id"
- stm-text="capability.name">cap</option>
- </select></div>
- </div>
- <div class="dialog-footer">
- <button type="button" class="btn-ghost"
- onclick="window.closeAppDialog('task-create-dialog')">Cancel</button>
- <button type="submit" class="btn-primary">Create task</button>
- </div>
- </form>
- </dialog>
- </body>
- </html>
|