Allow a task Page view web view and add it to TODO

This commit is contained in:
Ian Gulliver
2026-10-21 15:54:42 -07:00
parent 8e865e69b2
commit 0e5480b1a5
3 changed files with 150 additions and 1 deletions
+2
View File
@@ -17,5 +17,7 @@
# M8 — macOS client # M8 — macOS client
- Page view for HTML
Build once the attached sheet over the lead pane (mockup 3b, task "Left-side task context") has landed. A task view that shows an HTML page in a `WKWebView` inside that sheet, with the task's transcript staying on the right. It opens from an HTML attachment on a question, from clicking an `.html` file in Files, or from a preview link. It reloads in place when the agent changes the file, and has a "↗ Browser" button that opens the page at full width in the default browser. One-file pages load through the worktree file endpoint; a page's neighboring CSS and image files need serving too. Wireframe: `mockups/html-preview.html`, option 2. `docs/macos.md`, "Decision", allows this one web view.
- Phone app - Phone app
A native client for the phone over the same API, sharing the macOS client's SwiftUI code where it can. Every surveyed tool has one (Claude, Codex, Cursor and Conductor apps, GitHub Mobile, Superset for iPhone); it needs its own layout, since two half-width panes don't fit a phone. A native client for the phone over the same API, sharing the macOS client's SwiftUI code where it can. Every surveyed tool has one (Claude, Codex, Cursor and Conductor apps, GitHub Mobile, Superset for iPhone); it needs its own layout, since two half-width panes don't fit a phone.
+1 -1
View File
@@ -4,7 +4,7 @@ Platform constraints, build requirements and SwiftUI behavior observed live.
## Decision ## Decision
**No web view, under any circumstances**: no `WKWebView`, no `WebView`, no HTML rendering of any kind, in the window, in a dialog, for Markdown, for diagrams, for the terminal or for previews. A link to a web page opens in the default browser. Mermaid needs a browser DOM, and its automatic layout can't give the banded, nested look of a hand-drawn architecture picture, so byre has its own ```` ```diagram ```` block (rows, nested boxes, a side column, routed arrows, named highlights), laid out on a grid by the app; its syntax is the `diagrams` knowledge-base article. It is byre's only diagram format: a ```` ```mermaid ```` block shows as code, and the workflow stage graphs are drawn by generating its source. Agents see their diagrams through the `render_diagram` tool, which runs the installed app's own binary as `Byre render-diagram` (source on stdin, PNG on stdout), so the picture is exactly what the transcript draws; that mode opens no window and never touches the app's iCloud-synced store, so it uses the default text size. byre's Markdown dialect (choice markers, `<details>` sections, `#/task/` links, worktree image paths, `NO_REPLY`) matches no library, so the app parses it itself. **One web view only**: a task's Page view, which shows an HTML file from the task's worktree or one of its exposed previews in a `WKWebView`, in the attached sheet over the lead pane. Nothing else renders HTML: no `WKWebView` or `WebView` in the window, a dialog, Markdown, diagrams or the terminal. Any other link to a web page opens in the default browser. Mermaid needs a browser DOM, and its automatic layout can't give the banded, nested look of a hand-drawn architecture picture, so byre has its own ```` ```diagram ```` block (rows, nested boxes, a side column, routed arrows, named highlights), laid out on a grid by the app; its syntax is the `diagrams` knowledge-base article. It is byre's only diagram format: a ```` ```mermaid ```` block shows as code, and the workflow stage graphs are drawn by generating its source. Agents see their diagrams through the `render_diagram` tool, which runs the installed app's own binary as `Byre render-diagram` (source on stdin, PNG on stdout), so the picture is exactly what the transcript draws; that mode opens no window and never touches the app's iCloud-synced store, so it uses the default text size. byre's Markdown dialect (choice markers, `<details>` sections, `#/task/` links, worktree image paths, `NO_REPLY`) matches no library, so the app parses it itself.
The app joins the tailnet as its own node with libtailscale and reaches byre's HTTPS origin on byre's node, so byre knows who is calling from the node itself. It never sends identity headers, and certificate validation is never relaxed. The app joins the tailnet as its own node with libtailscale and reaches byre's HTTPS origin on byre's node, so byre knows who is calling from the node itself. It never sends identity headers, and certificate validation is never relaxed.
+147
View File
@@ -0,0 +1,147 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>HTML Preview Options</title>
<style>
:root {
--bg: #eceef1; --frame: #f7f8fa; --line: #c9ced6; --ink: #1f2430; --muted: #5b6372;
--bar: #d9dde3; --task: #c2417a; --task-soft: rgba(194,65,122,.12); --lead: #2f6db5;
--bubble: #e1e6ee; --warn: #9a6a00; --warn-soft: rgba(196,140,0,.14); --page: #ffffff;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #15181e; --frame: #1c2028; --line: #353b47; --ink: #e4e7ec; --muted: #a3abb8;
--bar: #3a404c; --task: #f08ab5; --task-soft: rgba(240,138,181,.12); --lead: #7fb2ee;
--bubble: #26303f; --warn: #e8b84a; --warn-soft: rgba(232,184,74,.14); --page: #232833;
}
}
:root[data-theme="dark"] {
--bg: #15181e; --frame: #1c2028; --line: #353b47; --ink: #e4e7ec; --muted: #a3abb8;
--bar: #3a404c; --task: #f08ab5; --task-soft: rgba(240,138,181,.12); --lead: #7fb2ee;
--bubble: #26303f; --warn: #e8b84a; --warn-soft: rgba(232,184,74,.14); --page: #232833;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--ink); font: 15px/1.45 -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif; }
main { max-width: 1240px; margin: 0 auto; padding: 32px 16px 64px; }
h1 { font-size: 26px; margin: 0 0 6px; }
h2 { font-size: 19px; margin: 40px 0 6px; }
p.cap { color: var(--muted); margin: 0 0 14px; max-width: 900px; }
.screen { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; }
.app { flex: 1 1 640px; min-width: 0; border: 1px solid var(--line); border-radius: 10px; background: var(--frame); overflow: hidden; }
.app .win { display: grid; grid-template-columns: 1fr 1fr; height: 380px; }
.pane { border-right: 1px solid var(--line); display: flex; flex-direction: column; min-width: 0; }
.pane:last-child { border-right: 0; }
.tabs { display: flex; gap: 6px; padding: 7px 9px; border-bottom: 1px solid var(--line); font-size: 12px; color: var(--muted); }
.tabs span { padding: 2px 7px; border-radius: 5px; }
.tabs .on { background: var(--bar); color: var(--ink); }
.body { padding: 10px; display: flex; flex-direction: column; gap: 8px; flex: 1; min-height: 0; font-size: 12.5px; }
.msg { background: var(--bubble); border-radius: 8px; padding: 7px 9px; }
.q { border: 1px solid var(--warn); background: var(--warn-soft); border-radius: 8px; padding: 8px 10px; }
.q b { display: block; margin-bottom: 4px; }
.q .opt { margin: 2px 0; }
.link { color: var(--lead); text-decoration: underline; }
.sheet { flex: 1; display: flex; flex-direction: column; border-top: 3px solid var(--task); background: var(--task-soft); min-height: 0; }
.sheet .head { display: flex; justify-content: space-between; padding: 6px 9px; font-size: 12px; border-bottom: 1px solid var(--line); }
.sheet .head .t { color: var(--task); font-weight: 600; }
.page { flex: 1; margin: 8px; background: var(--page); border: 1px solid var(--line); border-radius: 6px; padding: 10px; overflow: hidden; font-size: 11.5px; }
.page h4 { margin: 0 0 6px; font-size: 13px; }
.mini { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; height: 70px; border: 1px solid var(--line); border-radius: 4px; margin: 6px 0; }
.mini div { border-right: 1px solid var(--line); }
.mini .s { background: var(--task-soft); border-top: 2px solid var(--task); }
.browser { flex: 0 1 420px; min-width: 260px; border: 1px solid var(--line); border-radius: 10px; background: var(--frame); overflow: hidden; }
.browser .bar { display: flex; gap: 6px; padding: 6px 9px; border-bottom: 1px solid var(--line); font-size: 11.5px; color: var(--muted); }
.browser .bar span { padding: 2px 7px; border-radius: 5px; background: var(--bar); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 130px; }
.browser .bar .on { color: var(--ink); background: var(--bg); border: 1px solid var(--line); }
.browser .page { height: 300px; }
.label { font-size: 12px; color: var(--muted); margin: 6px 2px; }
ul.gets { margin: 12px 0 0; padding-left: 20px; max-width: 980px; }
ul.gets li { margin: 3px 0; }
.mac { font-size: 11px; color: var(--muted); padding: 4px 9px; border-top: 1px solid var(--line); }
</style>
</head>
<body>
<main>
<h1>Where should an agent's HTML page show?</h1>
<p class="cap">Same case for both: the "Left-side task context" agent asks ian to pick between mockups 3a and 3b, which live in <code>mockups/left-layer.html</code> (a 25&nbsp;KB page whose sections are drawn by JavaScript and follow light/dark mode). Then it adds two more wireframes to the file and asks again.</p>
<h2>1. Browser, as today</h2>
<p class="cap">The agent calls <code>client_open</code>. The browser comes forward on murky over byre's window. Each call copies the file into a new folder, so the second round opens a second tab; the first tab still shows the old page.</p>
<div class="screen">
<div class="app">
<div class="win">
<div class="pane">
<div class="tabs"><span class="on">Lead</span><span>Notes</span><span>Workflows</span><span>Status</span></div>
<div class="body"><div class="msg">Lead: the Left-side task agent has a question for you.</div></div>
</div>
<div class="pane">
<div class="tabs"><span class="on">Transcript</span><span>Changes</span><span>Files</span></div>
<div class="body">
<div class="q"><b>Which middle ground, 3a or 3b?</b>
<div class="opt">( ) 3a — sheet covers the lead, no tabs</div>
<div class="opt">(•) 3b — attached sheet with the task's tabs</div>
<div style="color:var(--muted);margin-top:4px">The wireframes are open in your browser.</div>
</div>
</div>
</div>
</div>
<div class="mac">byre window — behind the browser until ian switches back</div>
</div>
<div class="browser">
<div class="bar"><span>…/open/7f3a/left-layer.html</span><span class="on">…/open/c91e/left-layer.html</span></div>
<div class="page">
<h4>Attached Sheet Mockups</h4>
<div>3a. Sheet covers the lead</div>
<div class="mini"><div class="s"></div><div></div></div>
<div>3b. Attached sheet with tabs</div>
<div class="mini"><div class="s"></div><div></div></div>
<div style="color:var(--muted)">Full width, real browser: JS, dark mode, ⌘F, zoom, print, dev tools.</div>
</div>
</div>
</div>
<ul class="gets">
<li><b>Gets:</b> nothing new to build or maintain; every page works exactly as written (JS, fonts, dark mode); full browser width and tools; the "no web view" rule stays.</li>
<li><b>Gives up:</b> the page and the question are in two windows; ian switches back and forth to compare 3a/3b against the options; stale tabs pile up after each revision; it takes the screen on murky every time.</li>
</ul>
<h2>2. Built-in page view in the left sheet</h2>
<p class="cap">The question carries the page as an attachment (or ian clicks <code>left-layer.html</code> in Files, or a preview link). The page opens in the attached sheet over the lead pane, the way 3b opens Changes or Files, with the question still on the right. When the agent edits the file, the sheet reloads in place. Needs a <code>WKWebView</code>.</p>
<div class="screen">
<div class="app" style="flex-basis:100%">
<div class="win" style="height:420px">
<div class="pane">
<div class="tabs"><span>Lead</span><span>Notes</span><span>Workflows</span><span>Status</span></div>
<div class="sheet">
<div class="head"><span class="t">Left-side task context · left-layer.html</span><span>↗ Browser &nbsp; ✕</span></div>
<div class="page">
<h4>Attached Sheet Mockups</h4>
<div>3a. Sheet covers the lead</div>
<div class="mini"><div class="s"></div><div></div></div>
<div>3b. Attached sheet with tabs</div>
<div class="mini"><div class="s"></div><div></div></div>
<div style="color:var(--muted)">Half the window wide. Reloads when the agent changes the file.</div>
</div>
</div>
</div>
<div class="pane">
<div class="tabs"><span class="on">Transcript</span><span>Changes</span><span>Files</span><span>Page</span></div>
<div class="body">
<div class="q"><b>Which middle ground, 3a or 3b?</b>
<div class="opt">( ) 3a — sheet covers the lead, no tabs</div>
<div class="opt">(•) 3b — attached sheet with the task's tabs</div>
<div style="margin-top:4px"><span class="link">left-layer.html</span> <span style="color:var(--muted)">— showing on the left</span></div>
</div>
</div>
</div>
</div>
<div class="mac">One byre window. Nothing else comes forward.</div>
</div>
</div>
<ul class="gets">
<li><b>Gets:</b> page and question side by side in one window; the page refreshes in place after each revision; nothing takes the screen; works the same on every Mac without copying files.</li>
<li><b>Gives up:</b> reverses "no web view, under any circumstances" in <code>docs/macos.md</code>; a WebKit view inside the app that runs agent-written JavaScript; half the window wide, so a 1240px-wide mockup page is cramped (a ↗ Browser button stays for that); a one-file page like left-layer.html loads through the existing worktree file endpoint, but a page with its own CSS or image files next to it needs those served too; one more view kind to keep working.</li>
</ul>
</main>
</body>
</html>