Kaynağa Gözat

feat: plain terminal view with green # lines, native dialog for quote-comments, plan review labels, popover URI fix

clanker 1 ay önce
ebeveyn
işleme
0a82bf1ca8

+ 66 - 0
src/pages/app.css

@@ -491,6 +491,72 @@ summary { cursor: pointer; font-weight: 600; }
 
 [hidden] { display: none !important; }
 
+/* Plain terminal view — single monospace block, no cards */
+
+.terminal-plain {
+    background: #1a1a2e;
+    border-radius: 6px;
+    color: #d4d4d4;
+    font-family: "SF Mono", "Fira Code", "Cascadia Code", Menlo, monospace;
+    font-size: 0.875rem;
+    line-height: 1.5;
+    max-height: 70vh;
+    overflow-y: auto;
+    padding: var(--grid-2);
+    white-space: pre-wrap;
+    word-break: break-all;
+}
+
+.term-comment {
+    color: #4ade80;
+    font-style: italic;
+}
+
+.term-cmd {
+    color: #7dd3fc;
+    font-weight: 600;
+}
+
+.term-input-text {
+    color: #a5b4fc;
+}
+
+.term-out {
+    color: #d4d4d4;
+}
+
+/* Comment dialog */
+
+dialog#quote-comment-dialog {
+    border: 1px solid var(--border);
+    border-radius: 8px;
+    box-shadow: 0 8px 32px #0006;
+    max-width: 40rem;
+    padding: var(--grid-3);
+    width: 90%;
+}
+
+dialog#quote-comment-dialog::backdrop {
+    background: #0008;
+}
+
+dialog#quote-comment-dialog blockquote {
+    background: var(--bg-secondary);
+    border-left: 3px solid var(--accent);
+    font-family: "SF Mono", "Fira Code", Menlo, monospace;
+    font-size: 0.85rem;
+    margin: 0 0 var(--grid-2);
+    max-height: 10rem;
+    overflow-y: auto;
+    padding: var(--grid-2);
+    white-space: pre-wrap;
+}
+
+.term-comment-quote {
+    color: #4ade80;
+    font-style: italic;
+}
+
 @media (max-width: 40rem) {
     header { flex-direction: column; gap: var(--grid-1); }
     .cards { grid-template-columns: 1fr; }

+ 20 - 42
src/pages/main.html

@@ -51,7 +51,7 @@
                 };
 
                 document.addEventListener('mouseup', function (event) {
-                    if (quotePopover && quotePopover.contains(event.target)) return;
+                    if (quoteButton && quoteButton.contains(event.target)) return;
                     var selection = window.getSelection();
                     var text = selection ? String(selection.toString()).trim() : '';
                     activeHandler = matchHandler(event.target);
@@ -86,53 +86,31 @@
                     quoteButton.style.top = (y - 36) + 'px';
                     quoteButton.addEventListener('click', function (event) {
                         event.preventDefault();
-                        showQuotePopover(event.pageX, event.pageY);
+                        openQuoteDialog();
                     });
                     document.body.appendChild(quoteButton);
                 }
 
-                function showQuotePopover(x, y) {
+                function openQuoteDialog() {
                     if (quoteButton) { quoteButton.remove(); quoteButton = null; }
-                    quotePopover = document.createElement('div');
-                    quotePopover.className = 'quote-popover';
-                    quotePopover.style.left = Math.min(x, window.innerWidth - 120) + 'px';
-                    quotePopover.style.top = y + 'px';
-
-                    var quote = document.createElement('blockquote');
-                    quote.textContent = quotedText;
-                    quotePopover.appendChild(quote);
-
-                    var body = document.createElement('textarea');
-                    body.placeholder = 'Your comment on the selected text';
-                    quotePopover.appendChild(body);
-
-                    var actions = document.createElement('div');
-                    actions.className = 'actions';
-                    var submit = document.createElement('button');
-                    submit.type = 'button';
-                    submit.className = 'primary';
-                    submit.textContent = 'Send comment';
-                    submit.addEventListener('click', function () {
-                        var action = activeHandler && activeHandler.action();
-                        if (body.value.trim().length === 0 || !action) return;
-                        statum.act(action, {
-                            body: body.value,
-                            quote: quotedText,
-                            return_to: location.pathname + location.search
-                        });
-                        hideQuoteUi();
-                        if (window.getSelection) window.getSelection().removeAllRanges();
-                    });
-                    var cancel = document.createElement('button');
-                    cancel.type = 'button';
-                    cancel.textContent = 'Cancel';
-                    cancel.addEventListener('click', hideQuoteUi);
-                    actions.appendChild(submit);
-                    actions.appendChild(cancel);
-                    quotePopover.appendChild(actions);
+                    var dialog = document.getElementById('quote-comment-dialog');
+                    if (!dialog) return;
+                    var quote = dialog.querySelector('#quote-text');
+                    var field = dialog.querySelector('input[name="quote"]');
+                    if (quote) quote.textContent = quotedText;
+                    if (field) field.value = quotedText;
+                    var returnField = dialog.querySelector('input[name="return_to"]');
+                    if (returnField) returnField.value = location.pathname + location.search;
+                    if (typeof dialog.showModal === 'function') {
+                        dialog.showModal();
+                    } else {
+                        dialog.setAttribute('open', '');
+                    }
+                }
 
-                    document.body.appendChild(quotePopover);
-                    body.focus();
+                function hideQuoteUi() {
+                    if (quoteButton) { quoteButton.remove(); quoteButton = null; }
+                    if (quotePopover) { quotePopover.remove(); quotePopover = null; }
                 }
 
                 function hideQuoteUi() {

+ 1 - 1
src/pages/task_detail.html

@@ -222,7 +222,7 @@
                     <h3>Status</h3>
                     <p><span stm-attribute.class="'badge badge-' + task.status" stm-text="task.status">status</span>
                         · priority <strong stm-text="task.priority">5</strong>
-                        <span stm-if="task.is_design_task"> · <strong>design task</strong></span></p>
+                        <span stm-if="task.is_design_task"> · <strong>plan review required</strong></span></p>
                     <p class="muted">Created <span stm-text="task.created_display">when</span></p>
                     <p class="muted" stm-if="task.started_display.length > 0">Started <span stm-text="task.started_display">when</span></p>
                     <p class="muted" stm-if="task.completed_display.length > 0">Completed <span stm-text="task.completed_display">when</span></p>

+ 38 - 59
src/pages/task_terminal.html

@@ -4,40 +4,21 @@
     <pstm-uri>/tasks/{task_id}/terminal</pstm-uri>
     <pstm-template>main</pstm-template>
     <script>
-        // Live terminal: one EventSource to /terminal-stream/<session_id>
-        // renders every entry (initial backlog + new arrivals) as a typed
-        // card with collapsible output. Text highlighting inside the log
-        // offers the shared comment popover (see main.html).
         if (window.statum) {
             (function () {
                 'use strict';
 
-                var TERMINAL_TYPES = {
-                    'command': 'bash',
-                    'comment': 'llm-comment',
-                    'block': 'block',
-                    'user-comment': 'user-comment',
-                    'progress': 'progress',
-                    'review': 'review',
-                    'plan': 'plan',
-                    'shell-reset': 'Shell was reset',
-                    'failed': 'failure',
-                    'query': 'query'
-                };
-
                 var connected = false;
-                var commentAction = null;
 
                 statum.addStateListener('terminal', function () {
                     var state = statum.read('terminal');
                     if (!connected && state && state.exists && state.session_id > 0) {
-                        commentAction = state.comment;
                         if (window.initQuoteComments) {
                             window.initQuoteComments({
                                 matches: function (node) {
                                     return node.closest && node.closest('#term-log') != null;
                                 },
-                                action: function () { return commentAction; }
+                                action: function () { return state.comment; }
                             });
                         }
                         connect(state.session_id);
@@ -58,58 +39,44 @@
                         var placeholder = log.querySelector('.term-placeholder');
                         if (placeholder) placeholder.remove();
                         var nearBottom = log.scrollHeight - log.scrollTop - log.clientHeight < 48;
-                        log.appendChild(renderEntry(entry));
+                        appendEntry(log, entry);
                         if (nearBottom) log.scrollTop = log.scrollHeight;
                     };
                 }
 
-                function renderEntry(entry) {
-                    var type = TERMINAL_TYPES[entry.type] || entry.type || 'command';
-                    var card = document.createElement('div');
-                    card.className = 'term-entry type-' + (entry.type || 'command');
+                function appendEntry(log, entry) {
+                    var type = entry.type || 'command';
 
-                    var head = document.createElement('div');
-                    head.className = 'term-head';
-                    var badge = document.createElement('span');
-                    badge.className = 'badge badge-' + (entry.type || 'command');
-                    badge.textContent = type;
-                    head.appendChild(badge);
-                    if (entry.id) {
-                        var idLabel = document.createElement('span');
-                        idLabel.textContent = '#' + entry.id;
-                        head.appendChild(idLabel);
+                    if (type === 'llm-comment' || (entry.command && entry.command.charAt(0) === '#')) {
+                        var c = document.createElement('span');
+                        c.className = 'term-comment';
+                        c.textContent = (entry.command || entry.output || '') + '\n';
+                        log.appendChild(c);
+                        return;
                     }
-                    card.appendChild(head);
 
                     if (entry.command && entry.command.length > 0) {
-                        var command = document.createElement('div');
-                        command.className = 'term-command';
-                        command.textContent = entry.command;
-                        card.appendChild(command);
+                        var cmd = document.createElement('span');
+                        cmd.className = 'term-cmd';
+                        cmd.textContent = '$ ' + entry.command + '\n';
+                        log.appendChild(cmd);
                     }
 
                     if (entry.input && entry.input.length > 0) {
-                        var input = document.createElement('pre');
-                        input.className = 'term-input';
-                        input.textContent = entry.input;
-                        if (entry.input.split('\n').length > 5) input.classList.add('collapsed');
-                        input.addEventListener('click', function () {
-                            input.classList.toggle('collapsed');
-                        });
-                        card.appendChild(input);
+                        var inp = document.createElement('span');
+                        inp.className = 'term-input-text';
+                        inp.textContent = entry.input + '\n';
+                        log.appendChild(inp);
                     }
 
                     if (entry.output && entry.output.length > 0) {
-                        var output = document.createElement('pre');
-                        output.className = 'term-output';
-                        output.textContent = entry.output;
-                        if (entry.output.split('\n').length > 5) output.classList.add('collapsed');
-                        output.addEventListener('click', function () {
-                            output.classList.toggle('collapsed');
-                        });
-                        card.appendChild(output);
+                        var out = document.createElement('span');
+                        out.className = 'term-out';
+                        out.textContent = entry.output + '\n';
+                        log.appendChild(out);
                     }
-                    return card;
+
+                    log.appendChild(document.createTextNode('\n'));
                 }
             })();
         }
@@ -135,10 +102,22 @@
                 </span>
             </p>
 
-            <div class="terminal" stm-if="terminal.sessions.length > 0" id="term-log">
-                <p class="muted term-placeholder" stm-if="terminal.sessions.length > 0">Connecting to the live stream…</p>
+            <div class="terminal-plain" stm-if="terminal.sessions.length > 0" id="term-log">
+                <span class="muted term-placeholder">Connecting…</span>
             </div>
             <p stm-if="terminal.sessions.length == 0" class="muted">No sessions recorded for this task yet.</p>
+
+            <dialog id="quote-comment-dialog">
+                <h3>Comment on selected text</h3>
+                <blockquote id="quote-text" class="term-comment-quote"></blockquote>
+                <form stm-action="terminal.comment">
+                    <input type="hidden" name="quote">
+                    <input type="hidden" name="return_to">
+                    <textarea name="body" rows="4" placeholder="Your comment…" style="width:100%; margin:0.5em 0;"></textarea>
+                    <button type="submit" class="primary">Send comment</button>
+                    <button type="button" onclick="document.getElementById('quote-comment-dialog').close()">Cancel</button>
+                </form>
+            </dialog>
         </div>
     </section>
 </body>

+ 6 - 3
src/pages/tasks.html

@@ -75,7 +75,7 @@
                     <tr stm-for-task-in="tasks.tasks">
                         <td stm-text="task.id">id</td>
                         <td><a stm-attribute.href="'/tasks/' + task.id" stm-text="task.title">title</a>
-                            <span class="muted" stm-if="task.is_design_task"> · design</span>
+                            <span class="muted" stm-if="task.is_design_task"> · plan review</span>
                             <span class="muted" stm-if="task.parent_task_id > 0"> · subtask of
                                 <a stm-attribute.href="'/tasks/' + task.parent_task_id" stm-text="'#' + task.parent_task_id">#parent</a></span>
                             <span class="muted" stm-if="task.ready"> · ready</span></td>
@@ -112,8 +112,11 @@
                         <input type="text" name="branch_from" id="create-branch-from"
                                placeholder="project default branch"></div>
                     <p class="muted">Branch fields are inherited by subtasks unless overridden.</p>
-                    <div class="form-row"><label>Design task</label>
-                        <input type="checkbox" name="is_design_task"></div>
+                    <div class="form-row"><label>Plan review</label>
+                        <label class="option-card">
+                            <input type="checkbox" name="is_design_task">
+                            <span>Require plan approval — the agent explores, asks questions, writes a plan, and waits for your approval before writing code</span>
+                        </label></div>
                     <button type="submit" class="primary">Create task</button>
                 </form>
             </details>