oxedyne/daimond/www/index.html
111 KiB, 48 runs
created by r2519314175:1337, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | <!DOCTYPE html> |
| 2 | <html lang="en" data-theme="dark" data-tone="dark" data-ink="light" data-skin="sharp"> |
| 3 | <head> |
| 4 | <meta charset="UTF-8"> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> |
| 6 | <title>Daimond</title> |
| 7 | <!-- Apply the saved theme (palette) and skin (shape) before the first paint, so |
| 8 | a returning user never sees the default flash to their own choice. The full |
| 9 | services (DaimondTheme / DaimondSkin) take over once the app boots. --> |
| 10 | <script> |
| 11 | (function () { |
| 12 | try { |
| 13 | // The palette table, mirrored from THEMES in daimond.js because this runs |
| 14 | // before any script loads. Each entry is [tone, ink]: the band it is |
| 15 | // offered under, and whether its surfaces take dark or light lettering. |
| 16 | // verify_theme asserts the two tables agree, so the copy cannot drift. |
| 17 | var P = { |
| 18 | light: ['light', 'dark'], mist: ['light', 'dark'], linen: ['light', 'dark'], |
| 19 | lollypop: ['mid', 'dark'], sage: ['mid', 'dark'], dusk: ['mid', 'light'], |
| 20 | dark: ['dark', 'light'], amber: ['dark', 'light'], |
| 21 | midnight: ['dark', 'light'], |
| 22 | forest: ['dark', 'light'], plum: ['dark', 'light'], |
| 23 | }; |
| 24 | var t = localStorage.getItem('daimond-theme'); |
| 25 | var spec = P[t] || P.dark; |
| 26 | if (P[t]) document.documentElement.setAttribute('data-theme', t); |
| 27 | document.documentElement.setAttribute('data-tone', spec[0]); |
| 28 | document.documentElement.setAttribute('data-ink', spec[1]); |
| 29 | var s = localStorage.getItem('daimond-skin'); |
| 30 | if (s === 'soft') s = 'warm'; // the warm skin was briefly called "soft". |
| 31 | if (s === 'warm' || s === 'sharp') document.documentElement.setAttribute('data-skin', s); |
| 32 | // The language too, so the document declares itself before a screen |
| 33 | // reader or the browser's own translation offer looks at it. |
| 34 | var l = localStorage.getItem('daimond-locale'); |
| 35 | if (l) document.documentElement.lang = l; |
| 36 | } catch (e) { /* private mode: the default attributes stand. */ } |
| 37 | })(); |
| 38 | </script> |
| 39 | <link rel="icon" type="image/svg+xml" href="assets/daimond_mark.svg"> |
| 40 | <!-- Installable on a home screen. `app.webmanifest`, NOT `manifest.json`: that |
| 41 | name is taken, by the transparency manifest -- the SHA-256 of every served |
| 42 | file, which verify/check.mjs and js/verify.js read to prove the code in this |
| 43 | browser is the published source. Two different manifests, one of them much |
| 44 | older, so the newcomer is the one that moves. --> |
| 45 | <link rel="manifest" href="app.webmanifest"> |
| 46 | <!-- The colour the platform paints its own furniture with in standalone mode. |
| 47 | js/pwa.js keeps it on whichever of the eleven palettes is in use; this is the |
| 48 | default, for the moment before any script runs. --> |
| 49 | <meta name="theme-color" content="#15120F"> |
| 50 | <!-- iOS reads its own tags rather than the manifest. `apple-touch-icon` is the |
| 51 | home-screen icon (SVG is not honoured, hence the PNG), and "capable" is what |
| 52 | makes a pinned Daimond open without Safari's chrome. |
| 53 | |
| 54 | The status bar stays `default` -- iOS then keeps its own bar above the page |
| 55 | rather than letting the page run under it. `black-translucent` would look |
| 56 | better on the dark palettes and unreadable on the light ones, because it |
| 57 | forces white status text whatever the page is wearing, and Daimond ships |
| 58 | eleven palettes of which three are paper. The safe-area insets still matter |
| 59 | in every style: the home indicator, and the notch in landscape. --> |
| 60 | <link rel="apple-touch-icon" href="assets/icons/icon-180.png"> |
| 61 | <meta name="apple-mobile-web-app-capable" content="yes"> |
| 62 | <meta name="mobile-web-app-capable" content="yes"> |
| 63 | <meta name="apple-mobile-web-app-status-bar-style" content="default"> |
| 64 | <meta name="apple-mobile-web-app-title" content="Daimond"> |
| 65 | <link rel="stylesheet" href="css/variables.css"> |
| 66 | <link rel="stylesheet" href="css/app.css"> |
| 67 | <link rel="stylesheet" href="css/render.css"> |
| 68 | <!-- crystal.css AFTER app.css, deliberately. Both sheets name classes beginning |
| 69 | `crystal-`: app.css dresses the chrome around the frame (the ask row, the ✎ |
| 70 | form), crystal.css dresses the frame, the built-in view and the note that |
| 71 | says a page failed. Where the two ever named the same class, the later sheet |
| 72 | is the frame's, which is the half that owns the appearance. --> |
| 73 | <link rel="stylesheet" href="css/crystal.css"> |
| 74 | <!-- viewer.css before files.css: the file viewer draws inside the Doc panel that |
| 75 | sheet dresses, so the panel's own rules are the ones that win. --> |
| 76 | <link rel="stylesheet" href="css/viewer.css"> |
| 77 | <link rel="stylesheet" href="css/files.css"> |
| 78 | <link rel="stylesheet" href="css/mail.css"> |
| 79 | <link rel="stylesheet" href="css/graph.css"> |
| 80 | <link rel="stylesheet" href="css/tools.css"> |
| 81 | <link rel="stylesheet" href="css/autoreload.css"> |
| 82 | <link rel="stylesheet" href="css/models.css"> |
| 83 | <link rel="stylesheet" href="css/updater.css"> |
| 84 | <link rel="stylesheet" href="css/spend.css"> |
| 85 | <link rel="stylesheet" href="css/passkey.css"> |
| 86 | <!-- The beta passcode: the dialog a refused registration raises, and the block |
| 87 | it leaves behind in the Credits drawer. --> |
| 88 | <link rel="stylesheet" href="css/passcode.css"> |
| 89 | <link rel="stylesheet" href="css/workspace.css"> |
| 90 | <link rel="stylesheet" href="css/trash.css"> |
| 91 | <link rel="stylesheet" href="css/improve.css"> |
| 92 | <link rel="stylesheet" href="css/tracker.css"><!-- js/tracker.js — the Proposals tracker view --> |
| 93 | <link rel="stylesheet" href="css/terminal.css"> |
| 94 | <link rel="stylesheet" href="css/lapse.css"> |
| 95 | <link rel="stylesheet" href="css/release.css"> |
| 96 | <link rel="stylesheet" href="css/responsive.css"> |
| 97 | <link rel="stylesheet" href="css/mobile.css"> |
| 98 | <!-- Full-screen toggle (phone only) -- just the two icon faces; see js/fullscreen.js. --> |
| 99 | <link rel="stylesheet" href="css/fullscreen.css"> |
| 100 | <link rel="stylesheet" href="css/skin-warm.css"> |
| 101 | </head> |
| 102 | <body data-mpanel="ai"> |
| 103 | |
| 104 | <!-- Main app: browser-only, no server. The whole agent runs in wasm. --> |
| 105 | <div id="app"> |
| 106 | |
| 107 | <!-- What the copy buttons say to a screen reader. They are icon-only and |
| 108 | their confirmation is a tick, which is nothing at all to a reader that |
| 109 | does not see; one polite region serves the lot. --> |
| 110 | <div class="vh" id="copy-say" role="status" aria-live="polite"></div> |
| 111 | |
| 112 | <!-- Top bar --> |
| 113 | <header class="topbar" aria-labelledby="app-title"> |
| 114 | <h1 class="vh" id="app-title">Daimond</h1> |
| 115 | <!-- The hamburger: phone only, opens the rail as a left drawer. --> |
| 116 | <button id="drawer-btn" title="Menu" aria-label="Open menu" data-i18n-title="topbar.menu" data-i18n-aria-label="topbar.open_menu"> |
| 117 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/></svg> |
| 118 | </button> |
| 119 | <div class="brand-group"> |
| 120 | <a class="brand" href="https://daimond.app" target="_blank" rel="noopener noreferrer" aria-label="Daimond, the daimond.app home page" data-i18n-aria-label="topbar.brand"> |
| 121 | <img class="brand-wordmark wm-on-dark" src="assets/daimond_word.svg" alt="Daimond"> |
| 122 | <img class="brand-wordmark wm-on-light" src="assets/daimond_word_dark.svg" alt="" aria-hidden="true"> |
| 123 | </a> |
| 124 | </div> |
| 125 | <div class="top-actions"> |
| 126 | <!-- A tag per CLOSED panel. Click it to open the panel; the panel's own |
| 127 | closer sends it back here. --> |
| 128 | <div class="panel-tags" id="panel-tags"></div> |
| 129 | <button class="update-chip" id="update-chip" hidden title="Daimond is up to date" aria-label="Daimond is up to date" data-i18n-title="topbar.up_to_date" data-i18n-aria-label="topbar.up_to_date"> |
| 130 | <svg class="ic ic-refresh" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 10-.6 4"/><path d="M20 4v5h-5"/></svg> |
| 131 | <svg class="ic ic-check" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7"/></svg> |
| 132 | </button> |
| 133 | <!-- About, in the slot the maker's badge held. The badge said who made |
| 134 | Daimond and how, in a 36px lozenge with two invisible hit areas |
| 135 | over it: two claims that nobody could read and that a phone dropped |
| 136 | entirely. Both survive, in the dialog this opens, where each has |
| 137 | room to be a sentence. |
| 138 | |
| 139 | The glyph is an "i" in a circle, which is what "about" is drawn as |
| 140 | everywhere. The guide button beside it is a "?" in a circle, so the |
| 141 | two are deliberately separated by where the dot sits: the "i" wears |
| 142 | its dot on TOP of the stem, the "?" underneath its hook. Two |
| 143 | buttons in one row that read the same are two buttons that do the |
| 144 | same thing, as far as the user is concerned. --> |
| 145 | <button class="icon-btn" id="about-btn" title="About Daimond" aria-label="About Daimond" |
| 146 | data-i18n-title="topbar.about" data-i18n-aria-label="topbar.about"> |
| 147 | <!-- The dot is r=1.2 and not the "?"'s r=0.6, and the stem starts 2.4 |
| 148 | units below it. At 18px that is the difference between a dotted |
| 149 | "i" and a plain vertical bar: 8x crops of both glyphs were read |
| 150 | side by side, and the first attempt's r=0.6 dot merged into the |
| 151 | stem at every size the bar draws. --> |
| 152 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="7.4" r="1.2" fill="currentColor" stroke="none"/><path d="M12 11.6v5"/></svg> |
| 153 | </button> |
| 154 | <button class="icon-btn" id="guide-btn" title="Open the user guide" aria-label="User guide" data-i18n-title="topbar.guide" data-i18n-aria-label="topbar.guide_short"> |
| 155 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M9.2 9.3a2.8 2.8 0 115.3 1.2c-.4.9-1.5 1.3-2 2-.3.4-.3.9-.3 1.5"/><circle cx="12" cy="17.4" r="0.6" fill="currentColor" stroke="none"/></svg> |
| 156 | </button> |
| 157 | <button class="icon-btn menu-btn" id="settings-menu-btn" title="Appearance and layout" |
| 158 | aria-label="Appearance and layout" aria-haspopup="menu" aria-expanded="false" aria-controls="settings-menu" |
| 159 | data-i18n-title="topbar.appearance" data-i18n-aria-label="topbar.appearance"> |
| 160 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h2M10 17h10"/><circle cx="16" cy="7" r="2.2"/><circle cx="8" cy="17" r="2.2"/></svg> |
| 161 | </button> |
| 162 | <!-- Full screen: PHONE ONLY, and only where the browser offers the |
| 163 | Fullscreen API. Firefox on Android will not install the PWA |
| 164 | standalone, so this is the one route to an immersive view there; |
| 165 | desktop has F11 and a real install and never sees it. Shown, |
| 166 | hidden and kept in sync by js/fullscreen.js -- the inline |
| 167 | display:none is the off state the script clears on a phone. The |
| 168 | two faces are the crystal button's corner arrows: out to enter, |
| 169 | in to exit. --> |
| 170 | <button class="icon-btn" id="fullscreen-btn" type="button" style="display:none" |
| 171 | aria-pressed="false" title="Full screen" aria-label="Full screen"> |
| 172 | <svg class="ic ic-fs-enter" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 4H4v5M15 4h5v5M15 20h5v-5M9 20H4v-5"/></svg> |
| 173 | <svg class="ic ic-fs-exit" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9h5V4M20 9h-5V4M20 15h-5v5M4 15h5v5"/></svg> |
| 174 | </button> |
| 175 | </div> |
| 176 | </header> |
| 177 | |
| 178 | <!-- Every panel there is, searchable, for when the chip row cannot hold |
| 179 | them all. Rows carry a pin, which is what decides the row's contents: |
| 180 | the user says what sits in the header and nothing reorders itself. --> |
| 181 | <div class="pop" id="panel-gallery" hidden role="dialog" aria-label="Panels" data-i18n-aria-label="gallery.title"></div> |
| 182 | |
| 183 | <!-- Appearance and layout: theme, reading size, dock tiling. --> |
| 184 | <div class="pop" id="settings-menu" hidden role="dialog" aria-label="Appearance and layout" data-i18n-aria-label="topbar.appearance"></div> |
| 185 | <!-- The permission ladder. A `.pop`, like the appearance menu and the panel |
| 186 | gallery, so Escape and a click outside dismiss it exactly as they do — |
| 187 | and a radio group inside, so the arrow keys work without a line of code. --> |
| 188 | <div class="pop mode-pop" id="hand-mode-pop" hidden role="dialog" |
| 189 | aria-label="Permission mode" data-i18n-aria-label="permmode.title"></div> |
| 190 | |
| 191 | <!-- The keyboard's way in. Complete at any fleet size, which is what lets |
| 192 | the visible surfaces be selective. --> |
| 193 | <div class="pal-scrim" id="palette" hidden> |
| 194 | <div class="pal-box" role="dialog" aria-modal="true" aria-label="Go to" data-i18n-aria-label="pal.title"> |
| 195 | <!-- The search box and the way out, on one row. The palette is opened |
| 196 | with a key and was closed with one, which is right for the hands |
| 197 | that summoned it and no help at all to a thumb: on a phone the box |
| 198 | is 358px of a 390px screen. --> |
| 199 | <div class="pal-top"> |
| 200 | <input class="pal-input" id="pal-input" type="text" role="combobox" autocomplete="off" |
| 201 | aria-expanded="true" aria-controls="pal-list" aria-autocomplete="list" |
| 202 | placeholder="Go to a panel, or a setting…" data-i18n-placeholder="pal.placeholder"> |
| 203 | <button class="ui-close pal-close" id="pal-close" type="button" title="Close" |
| 204 | aria-label="Close the Go to box" data-i18n-title="common.close" data-i18n-aria-label="pal.close"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button> |
| 205 | </div> |
| 206 | <ul class="pal-list" id="pal-list" role="listbox" aria-label="Results" data-i18n-aria-label="pal.results"></ul> |
| 207 | </div> |
| 208 | </div> |
| 209 | |
| 210 | <!-- Four panels --> |
| 211 | <div class="main" id="main"> |
| 212 | |
| 213 | <!-- Rail: two panes, split by a handle the user can move but not remove. |
| 214 | Above: what you are working on. Below: the Admin panel — who you are, |
| 215 | how Daimond is set up, and what it is costing. Settings and the forms |
| 216 | that configure Daimond live there rather than in a popup, so a chat |
| 217 | stays live beside them: the user can ask Daimond what an app password is |
| 218 | while the box asking for one is still on screen. --> |
| 219 | <aside class="panel rail" id="panel-rail" data-panel="rail" data-zone="rail" data-label="Diamonds" data-i18n-label="panel.rail"> |
| 220 | <div class="rail-top" id="rail-top"> |
| 221 | <!-- The global pause, which is not a seventh setting: it is the ROOT |
| 222 | of the tree every other traffic light hangs off, so it is green |
| 223 | only while everything that could spend is running, red only when |
| 224 | none of it is, and amber the rest of the time. It sits above the |
| 225 | Diamonds list because that is the first thing the eye reaches, |
| 226 | and because a hold on everything belongs over everything. |
| 227 | The control itself is built in daimond.js, which owns the DOM. --> |
| 228 | <div class="pptw-head" id="pptw-global-row"> |
| 229 | <span class="pptw-slot" id="pptw-global"></span> |
| 230 | <span class="pptw-head-label" title="Pause or resume everything that can spend: every Diamond, every chat, the workers, the mailboxes and any page fetched for you." data-i18n="pause.everything" data-i18n-title="pause.everything_help">Everything</span> |
| 231 | <!-- The rail's closer, and it is the RAIL it closes. It stood on the |
| 232 | Diamonds head below, in the run of buttons that make a Diamond |
| 233 | and draw the graph, where it read as the closer of that section: |
| 234 | a cross at the end of a row named "Diamonds" says it puts the |
| 235 | Diamonds away, and it took the whole rail — the Chats, the |
| 236 | status header and the Admin panel with it. This row is the only |
| 237 | one in the rail that speaks for all of it, so the closer belongs |
| 238 | here, above everything it acts on and beside the pause that has |
| 239 | the same scope. `data-close` and the classes are unchanged; the |
| 240 | handler in daimond.js binds by `[data-close]` and does not care |
| 241 | where the button sits. --> |
| 242 | <button class="addbtn panel-close ui-close" data-close="rail" title="Close panel" |
| 243 | aria-label="Close the Diamonds panel" data-i18n-title="common.close_panel" data-i18n-aria-label="rail.close_rail"> |
| 244 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 245 | </button> |
| 246 | </div> |
| 247 | <!-- Diamonds above, Chats below, which is the way the ground is: |
| 248 | you mine in the conversation underneath and what you bring up |
| 249 | is cut and polished above it. The order is not decoration -- |
| 250 | a chat is raw and a Diamond is worked, and the rail says so |
| 251 | without needing a word of explanation. --> |
| 252 | <div class="railhead"><span role="heading" aria-level="2" title="A repeatable method, distilled from a conversation." data-i18n="rail.diamonds" data-i18n-title="rail.diamonds_help">Diamonds</span> |
| 253 | <button class="addbtn" id="new-diamond-btn" title="New Diamond" data-i18n-title="rail.new_diamond"> |
| 254 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg> |
| 255 | </button> |
| 256 | <!-- The Diamonds and the links between them, drawn. It belongs on |
| 257 | the rail head because the rail is the list the picture is of. |
| 258 | It TOGGLES the Graph rather than only opening it, so it is a |
| 259 | latched button and says so: `aria-pressed` starts false because |
| 260 | nothing is open before the layout engine has read the stored |
| 261 | layout, and daimond.js sets it from there and on every press. |
| 262 | The latched look is `.addbtn[aria-pressed="true"]` in app.css. --> |
| 263 | <button class="addbtn" id="link-graph-btn" aria-pressed="false" title="Graph" aria-label="Graph" data-i18n-title="link.graph_open" data-i18n-aria-label="link.graph_open"> |
| 264 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="5.5" r="2.5"/><circle cx="5.5" cy="18" r="2.5"/><circle cx="18.5" cy="18" r="2.5"/><path d="M10.6 7.6L6.9 15.9M13.4 7.6l3.7 8.3M8 18h8"/></svg> |
| 265 | </button> |
| 266 | <!-- No closer on this row. It is one section of the rail and has |
| 267 | nothing of its own to close; the rail's closer is on the |
| 268 | Everything row above. --> |
| 269 | </div> |
| 270 | <!-- Narrow the rail by tag. A search box stood here too and has |
| 271 | gone: a Diamond is found by what it is filed under, and two |
| 272 | ways of narrowing one short list is one more than it needs. --> |
| 273 | <div class="diamond-find"> |
| 274 | <div class="diamond-filter" id="diamond-filter" style="display:none"></div> |
| 275 | </div> |
| 276 | <div class="diamond-list" id="diamond-list"></div> |
| 277 | <!-- The divider between the two lists. Diamonds accumulate, and the |
| 278 | list used to grow until the chat tiles were off the bottom of the |
| 279 | rail with no way to get them back. --> |
| 280 | <div class="rhandle" id="handle-rail-split" title="Drag to resize, double-click to reset" data-i18n-title="layout.handle"></div> |
| 281 | <div class="railhead"><span role="heading" aria-level="2" title="Raw conversation. A Diamond is cut from it." data-i18n="rail.chats" data-i18n-title="rail.chats_help">Chats</span> |
| 282 | <button class="addbtn" id="new-session-btn" title="New chat" data-i18n-title="rail.new_chat"> |
| 283 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg> |
| 284 | </button> |
| 285 | <!-- The section's own overflow, for an act that is about every chat |
| 286 | rather than one -- "Delete all chats" lives behind this, not |
| 287 | beside the "+" above. A cross next to "+" would have been a |
| 288 | second closer that behaved nothing like the rest: every other |
| 289 | cross in this app removes the one thing beside it, and a cross |
| 290 | that removed everything below it would be the same glyph |
| 291 | promoted to a different, much larger act with no visible change |
| 292 | to warn a hand that has learned to trust it. daimond.js builds |
| 293 | the dropdown; nothing here decides what is in it. --> |
| 294 | <button class="addbtn" id="chats-menu-btn" title="More" data-i18n-title="rail.chats_menu" aria-haspopup="menu" aria-expanded="false"> |
| 295 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="5" cy="12" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="19" cy="12" r="1.8"/></svg> |
| 296 | </button> |
| 297 | </div> |
| 298 | <div class="session-list" id="session-list"></div> |
| 299 | </div> |
| 300 | |
| 301 | <div class="rhandle" id="handle-split" title="Drag to resize, double-click to reset" data-i18n-title="layout.handle"></div> |
| 302 | |
| 303 | <!-- The Admin panel. --> |
| 304 | <div class="admin" id="admin"> |
| 305 | |
| 306 | <!-- The status header: the state of the machine, at a glance and |
| 307 | without asking. Each row is the answer to a question the user |
| 308 | would otherwise have to open something to find out, and each |
| 309 | one that can be acted on is a button that goes there. --> |
| 310 | <div class="admin-status" id="admin-status"> |
| 311 | <div class="astat-row astat-id"> |
| 312 | <button class="user-row" id="user-row" title="Your Daimond identity" data-i18n-title="admin.identity"> |
| 313 | <span class="user-avatar" id="user-avatar" aria-hidden="true">◈</span> |
| 314 | <span class="user-info" id="user-info">Browser-only</span> |
| 315 | </button> |
| 316 | <!-- The same cog every Diamond tile and every mailbox row wears, |
| 317 | not the ⚙ character. A font glyph is drawn by whichever font |
| 318 | the platform finds it in, at whatever weight and size that |
| 319 | font gives it, so the one cog in the rail's status header |
| 320 | read as a different icon from the cogs directly above it. |
| 321 | The path is `COG_D` in js/daimond.js, which `DaimondUI.cogIcon` |
| 322 | hands to the tiles and to mail.js; this copy is inline because |
| 323 | the button is in the static markup and nothing builds it. Both |
| 324 | must move together if the drawing ever changes. |
| 325 | No size override: `.icon-btn .ic` and `.tile-cog .ic` both take |
| 326 | the 16px default, which is what makes the two identical. --> |
| 327 | <button class="icon-btn" id="settings-btn" title="Settings" data-i18n-title="admin.settings" aria-label="Settings" data-i18n-aria-label="admin.settings"> |
| 328 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 00.3 1.8l.1.1a2 2 0 11-2.8 2.8l-.1-.1a1.7 1.7 0 00-2.9 1.2v.2a2 2 0 11-4 0v-.1a1.7 1.7 0 00-1.1-1.5 1.7 1.7 0 00-1.9.3l-.1.1a2 2 0 11-2.8-2.8l.1-.1a1.7 1.7 0 00-1.2-2.9H2.9a2 2 0 110-4H3a1.7 1.7 0 001.5-1.1 1.7 1.7 0 00-.3-1.9l-.1-.1a2 2 0 112.8-2.8l.1.1a1.7 1.7 0 001.8.3H9a1.7 1.7 0 001-1.5V2.9a2 2 0 114 0V3a1.7 1.7 0 001 1.5 1.7 1.7 0 001.9-.3l.1-.1a2 2 0 112.8 2.8l-.1.1a1.7 1.7 0 00-.3 1.8V9a1.7 1.7 0 001.5 1H21a2 2 0 110 4h-.1a1.7 1.7 0 00-1.5 1z"/></svg> |
| 329 | </button> |
| 330 | </div> |
| 331 | <button class="astat-row astat-btn" id="astat-model"></button> |
| 332 | <button class="astat-row astat-btn" id="astat-account"></button> |
| 333 | <!-- The second money row: the user's own provider key, when they have |
| 334 | one. Two economies sit side by side here and each row names whose |
| 335 | money it is -- see js/money.js. --> |
| 336 | <button class="astat-row astat-btn" id="astat-byok" style="display:none"></button> |
| 337 | <button class="astat-row astat-btn" id="astat-pro"></button> |
| 338 | <button class="astat-row astat-btn" id="astat-tools"></button> |
| 339 | <button class="astat-row astat-btn" id="astat-hand"></button> |
| 340 | <!-- The version row carries a copy button for the build id, which |
| 341 | is the string a bug report has to quote. It sits OUTSIDE |
| 342 | #astat-release rather than inside it: that row is itself a |
| 343 | button (it opens the version history), and a button inside a |
| 344 | button is neither valid nor clickable. The row keeps its id |
| 345 | and its classes so release.js still paints it. --> |
| 346 | <div class="astat-copyrow"> |
| 347 | <button class="astat-row astat-btn" id="astat-release" title="Version history" data-i18n-title="admin.version_history"></button> |
| 348 | <span class="astat-copyslot" id="astat-release-copy"></span> |
| 349 | </div> |
| 350 | <div class="astat-row" id="astat-store"></div> |
| 351 | <!-- The real folder, when the browser can open one. Its size is not a thing the |
| 352 | browser will tell us, so it is asked for rather than assumed. --> |
| 353 | <div class="astat-row" id="astat-store-native" style="display:none"></div> |
| 354 | <!-- Whether this device's work has travelled, which was a pill in the |
| 355 | top bar until 2026-08-28. It sat in the row of icon buttons, so |
| 356 | it moved every one of them 86px each time a round started and |
| 357 | again when it finished; and it faded 1.8s after saying "Synced", |
| 358 | so the answer to "did that reach my other machine" was gone |
| 359 | before it was asked. Here it is a status row like its neighbours |
| 360 | -- one question, answered without opening anything -- and the row |
| 361 | is PERMANENT: `js/sync.js` puts its chip in when there is |
| 362 | something happening or something standing, and when there is |
| 363 | not, `#sync-rest` says when a sync last worked. Nothing above or |
| 364 | below it moves either way. --> |
| 365 | <div class="astat-row astat-sync" id="astat-sync"> |
| 366 | <span class="astat-dot" id="sync-rest-dot" aria-hidden="true"></span> |
| 367 | <span class="astat-val" id="sync-rest"></span> |
| 368 | </div> |
| 369 | <!-- Global spend readout: session / week / month, priced per model. --> |
| 370 | <div class="spend-row" id="spend-row" style="display:none"></div> |
| 371 | </div> |
| 372 | |
| 373 | <div class="admin-body" id="admin-body"> |
| 374 | <div class="admin-drawer-head"> |
| 375 | <span class="admin-title" id="admin-drawer-title">Admin</span> |
| 376 | <!-- The drawn cross, not the '×' character: a glyph's ink is not |
| 377 | centred in its own em box and which box it gets depends on |
| 378 | whichever font the platform found it in. `.ui-close` carries |
| 379 | the shape and the 44px thumb floor; `.admin-back` stays as the |
| 380 | hook the drawer's own rules and two verifiers reach for. --> |
| 381 | <button class="admin-back ui-close" id="admin-close" title="Close" aria-label="Close the Admin panel" |
| 382 | data-i18n-title="common.close" data-i18n-aria-label="admin.close_drawer"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button> |
| 383 | </div> |
| 384 | <div class="admin-scroll" id="admin-scroll"> |
| 385 | |
| 386 | <!-- Home: what the panel rests on. Built in JS, because what it |
| 387 | offers depends on whether there is an identity to offer it to. --> |
| 388 | <div class="admin-view" id="admin-home"></div> |
| 389 | |
| 390 | <!-- Models: a key per provider, and the one model a new chat starts on. |
| 391 | Reached from the "Models" status row above, which counts them. --> |
| 392 | <div class="admin-view" id="admin-models" style="display:none"> |
| 393 | <div class="settings-section"> |
| 394 | <p class="cfg-lead" data-i18n="models.lead">A key for each provider you use. New chats and Diamonds start on the default model; any model can be picked for a single chat.</p> |
| 395 | <div id="models-list" class="models-list"></div> |
| 396 | <!-- The panel's message line: a provider that would not say what is left, |
| 397 | a credit figure that is not a number, a models list that would not |
| 398 | load. It looked itself up in `models.js` for a month and NOTHING |
| 399 | anywhere created it, so every one of those messages was swallowed. |
| 400 | OUTSIDE `models-list`, because `render()` rewrites that list |
| 401 | wholesale and would carry the message off with it. Same |
| 402 | `login-error` line as `byok-note` below rather than a fourth |
| 403 | notice mechanism. --> |
| 404 | <div id="models-note" class="login-error" role="status" aria-live="polite"></div> |
| 405 | <button type="button" id="models-add" class="models-add" data-i18n="models.add">+ Add provider</button> |
| 406 | |
| 407 | <!-- Adding one: the same three questions, whoever the provider is. --> |
| 408 | <div id="byok-form" class="byok-form" style="display:none"> |
| 409 | <label class="cfg-fieldlabel" for="cfg-provider" data-i18n="models.provider">Provider</label> |
| 410 | <select id="cfg-provider" class="settings-select"> |
| 411 | <option value="" data-i18n="models.choose_provider">Choose a provider…</option> |
| 412 | <option value="anthropic">Anthropic</option> |
| 413 | <option value="fireworks">Fireworks AI</option> |
| 414 | <option value="openrouter">OpenRouter</option> |
| 415 | <option value="together">Together AI</option> |
| 416 | <option value="groq">Groq</option> |
| 417 | <option value="deepinfra">DeepInfra</option> |
| 418 | <option value="custom" data-i18n="models.custom_provider">Custom (advanced)…</option> |
| 419 | </select> |
| 420 | <div id="cfg-url-row" style="display:none;"> |
| 421 | <label class="cfg-fieldlabel" for="cfg-base-url" data-i18n="models.base_url">Base URL</label> |
| 422 | <input type="text" id="cfg-base-url" placeholder="https://…/v1/chat/completions" spellcheck="false" autocomplete="off"> |
| 423 | </div> |
| 424 | <label class="cfg-fieldlabel" for="cfg-api-key" data-i18n="models.api_key">API key</label> |
| 425 | <input type="text" id="cfg-api-key" placeholder="Paste your API key" data-i18n-placeholder="models.api_key_ph" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" data-1p-ignore data-lpignore="true"> |
| 426 | <label class="cfg-fieldlabel" for="cfg-model" data-i18n="models.default_model">Default model</label> |
| 427 | <select id="cfg-model" class="settings-select"> |
| 428 | <option value="" data-i18n="models.choose_provider_first">Choose a provider first…</option> |
| 429 | </select> |
| 430 | <input type="text" id="cfg-model-custom" placeholder="Model id (type manually)" spellcheck="false" style="display:none;" data-i18n-placeholder="models.model_id_ph"> |
| 431 | <p class="cfg-fieldnote" data-i18n="models.default_note">New agents use this unless you choose another for them.</p> |
| 432 | <button type="button" id="byok-save" data-i18n="models.save_start">Save & start</button> |
| 433 | </div> |
| 434 | <div id="byok-note" class="login-error"></div> |
| 435 | <p class="cfg-hint" data-i18n-html="models.privacy"><strong>Everything stays on this device</strong> — your keys, chats and files live only in this browser; nothing is sent to us.</p> |
| 436 | </div> |
| 437 | |
| 438 | <!-- Search: which outside service the agent searches with. |
| 439 | Directly under the model picker because it is the same kind of |
| 440 | decision — which outside service does my agent use — and it is |
| 441 | a SETTING rather than a line in a prompt file, because a prompt |
| 442 | line is advice a model may ignore and this has to be enforced. |
| 443 | |
| 444 | EVERY string here is painted by `SearchRow.render` in |
| 445 | js/daimond.js and NOT by `data-i18n`, which is deliberate and |
| 446 | is about shipping order. `data-i18n` writes `t(key)` into the |
| 447 | element, and `t` answers with the KEY when the table has no |
| 448 | entry — so a panel reading "search.head" until the i18n lane's |
| 449 | eight locales land. `tOr` answers with the English written at |
| 450 | the call site instead, so the two halves of this release are |
| 451 | safe to arrive in either order. The English below is what |
| 452 | shows before the first paint, and only then. --> |
| 453 | <div class="settings-section" id="search-section"> |
| 454 | <h3 id="search-head">Search</h3> |
| 455 | <div class="search-engine-row"> |
| 456 | <label class="cfg-fieldlabel" for="set-search-engine">Search engine</label> |
| 457 | <select id="set-search-engine" class="settings-select"></select> |
| 458 | <p class="search-engine-note" id="search-engine-note">Which service Daimond searches with. Most give you a free allowance each month if you bring your own key.</p> |
| 459 | </div> |
| 460 | <!-- Hidden for `credits`: the gateway holds that key and there is |
| 461 | nothing here for anyone to paste. --> |
| 462 | <div class="search-engine-row" id="search-key-row" style="display:none"> |
| 463 | <label class="cfg-fieldlabel" for="set-search-key">API key</label> |
| 464 | <!-- Masked by installSecretMask, exactly as the provider key |
| 465 | and the push token are, and for the same reason: a real |
| 466 | password field is what invites a browser or a manager to |
| 467 | file somebody else's bearer credential away. --> |
| 468 | <input type="text" id="set-search-key" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" data-1p-ignore data-lpignore="true"> |
| 469 | <p class="search-engine-note" id="search-key-note">Kept on this device, sealed with your passphrase, and sent only with the search it pays for.</p> |
| 470 | <p class="search-engine-note" id="search-key-warn" data-warn="1" style="display:none"></p> |
| 471 | </div> |
| 472 | </div> |
| 473 | |
| 474 | <!-- The Terminal's folder. A terminal is the user at a keyboard and a |
| 475 | command is a daimon, so the two may be different sizes -- and the |
| 476 | choice is made HERE rather than only in install.sh, because a |
| 477 | setting nobody can find is a setting that gets forgotten. |
| 478 | |
| 479 | The options are written by the MACHINE and read out of the hand's |
| 480 | hello as `terminal-ceiling:` entries. The page chooses among them |
| 481 | and cannot name a folder the machine did not offer, which is the |
| 482 | same rule a toolkit follows. Where the user pinned one at a shell |
| 483 | with `install.sh --terminal-workspace`, the row says so and the |
| 484 | select is disabled: a decision already made is not re-asked. --> |
| 485 | <!-- The folder the hand may work in, and the folder a terminal may reach. |
| 486 | Two sizes on purpose: a terminal is the user at a keyboard and a |
| 487 | command is a daimon. The walk is served by the hand over the |
| 488 | machine's own directories -- the browser's own picker answers with |
| 489 | a handle carrying a name and no path, and a fence needs the path. --> |
| 490 | <div class="settings-section" id="grantroot-section" style="display:none"> |
| 491 | <h3 id="grantroot-head" data-i18n="grantroot.head">Daimond's folder</h3> |
| 492 | <div class="search-engine-row"> |
| 493 | <code class="termroot-now" id="grantroot-now"></code> |
| 494 | <div class="termroot-acts"> |
| 495 | <button type="button" class="termroot-btn" id="grantroot-browse" data-i18n="termroot.browse">Change…</button> |
| 496 | </div> |
| 497 | <p class="search-engine-note" id="grantroot-note" data-i18n="grantroot.note">Bounds every command. Applies on reload.</p> |
| 498 | </div> |
| 499 | <div id="grantroot-walk" class="termroot-walk" style="display:none"></div> |
| 500 | </div> |
| 501 | |
| 502 | <div class="settings-section" id="termroot-section" style="display:none"> |
| 503 | <h3 id="termroot-head" data-i18n="termroot.head">Terminal folder</h3> |
| 504 | <div class="search-engine-row"> |
| 505 | <p class="search-engine-note" id="termroot-note" data-i18n="termroot.note">A terminal is you, so it may reach further than a daimon.</p> |
| 506 | <p class="search-engine-note" id="termroot-pinned" style="display:none"></p> |
| 507 | </div> |
| 508 | </div> |
| 509 | </div> |
| 510 | |
| 511 | <!-- Credits: reached from the "Credits" status row. --> |
| 512 | <div class="admin-view" id="admin-credits" style="display:none"> |
| 513 | <div class="settings-section" id="credits-section"> |
| 514 | <p class="cfg-lead" data-i18n="credits.lead">Buy credits and Daimond runs the model for you, with no provider key to manage. No subscription. You keep what you buy, and nothing is charged again unless you turn on auto-reload.</p> |
| 515 | <!-- The closed beta, above everything an account buys, because |
| 516 | while registration is shut it is the reason none of it is |
| 517 | available. Empty in the served HTML and filled by |
| 518 | js/passcode.js — what may honestly be said here depends on |
| 519 | what the gateway last answered, so there is nothing to write |
| 520 | until it has. `.beta-block:empty` takes the border away with |
| 521 | the content, so a device with an account sees no gap. --> |
| 522 | <div class="beta-block" id="credits-beta"></div> |
| 523 | <div class="pro-block" id="credits-pro"></div> |
| 524 | <div class="credits-balance" id="credits-balance"></div> |
| 525 | <div class="credits-packs" id="credits-packs"></div> |
| 526 | <div id="credits-note" class="login-error"></div> |
| 527 | <div id="autoreload" class="autoreload"></div> |
| 528 | </div> |
| 529 | </div> |
| 530 | |
| 531 | <!-- Push: the credential a `git push` travels with. Its own view and not a |
| 532 | row in Models, because a token that can WRITE to a repository is a |
| 533 | different kind of secret from a key that buys tokens from a provider, |
| 534 | and filing the two together invites pasting one into the other. |
| 535 | |
| 536 | Two boxes and no more. The user name a token travels as is a per-forge |
| 537 | constant rather than a choice -- `x-access-token` at GitHub, `oauth2` at |
| 538 | GitLab -- so it is inferred from the host; a third box asking for it |
| 539 | would mostly be filled in with a person's own login, which fails to |
| 540 | authenticate with a message that names neither box. --> |
| 541 | <div class="admin-view" id="admin-push" style="display:none"> |
| 542 | <div class="settings-section"> |
| 543 | <p class="cfg-lead" data-i18n="push.lead">A token lets Daimond push work you have committed. Without one it still commits, but every push is refused and the work stays on this machine.</p> |
| 544 | <div id="push-state" class="cfg-fieldnote"></div> |
| 545 | <label class="cfg-fieldlabel" for="cfg-push-host" data-i18n="push.host">Host</label> |
| 546 | <input type="text" id="cfg-push-host" placeholder="github.com" data-i18n-placeholder="push.host_ph" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false"> |
| 547 | <p class="cfg-fieldnote" data-i18n="push.host_note">Just the name, as in github.com. No https://, no port, no path. Only this host is authenticated; a repository anywhere else is refused, and your token never reaches it.</p> |
| 548 | <!-- Built exactly as #cfg-api-key is, and masked by installSecretMask |
| 549 | for the same reason: a real password field is what makes a browser |
| 550 | or a manager offer to save it, and this one belongs in neither. --> |
| 551 | <label class="cfg-fieldlabel" for="cfg-push-token" data-i18n="push.token">Token</label> |
| 552 | <input type="text" id="cfg-push-token" placeholder="Paste a token with push access" data-i18n-placeholder="push.token_ph" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" data-1p-ignore data-lpignore="true"> |
| 553 | <p class="cfg-fieldnote" data-i18n="push.token_note">Push access is enough; the token never needs more. Daimond only pushes, only fast-forward, only to origin. Force, delete, mirror and any other remote are refused whatever the token allows. Empty this box and save to remove it.</p> |
| 554 | <button type="button" id="push-save" data-i18n="push.save">Save</button> |
| 555 | <div id="push-note" class="login-error"></div> |
| 556 | <p class="cfg-hint" data-i18n="push.privacy">The token is encrypted under your passphrase and kept in this browser. It is never shown again, never sent to us, never given to a model. Without an account there is nothing to encrypt it with, so it is held for this tab only and asked for again after a reload.</p> |
| 557 | </div> |
| 558 | </div> |
| 559 | |
| 560 | <!-- A form, built on demand: a mailbox to add, a thing to configure. --> |
| 561 | <div class="admin-view" id="admin-form" style="display:none"></div> |
| 562 | |
| 563 | <!-- What you are running, and what came before. --> |
| 564 | <div class="admin-view" id="admin-release" style="display:none"> |
| 565 | <div class="rel-list" id="rel-list"></div> |
| 566 | </div> |
| 567 | </div> |
| 568 | </div> |
| 569 | </div> |
| 570 | </aside> |
| 571 | <div class="phandle" id="handle-rail" data-panel="rail" title="Drag to resize, double-click to reset" data-i18n-title="layout.handle"></div> |
| 572 | |
| 573 | <!-- ── The stage ─────────────────────────────────────────────── |
| 574 | The middle zone. Its occupants are EXCLUSIVE to it: they take the |
| 575 | stage, solo or two side by side, and never dock at the right. AI is |
| 576 | the default occupant and comes back whenever the stage would |
| 577 | otherwise be empty, because an app whose middle holds no |
| 578 | conversation is not Daimond. |
| 579 | |
| 580 | The point of two seats is that you never leave the conversation to |
| 581 | do a thing: read the message, watch the page, read the document — |
| 582 | with the daimon still there beside it, to be asked about it. --> |
| 583 | <main class="stage" id="stage"> |
| 584 | |
| 585 | <!-- AI: the conversation. --> |
| 586 | <section class="panel ai" id="panel-ai" data-panel="ai" data-zone="stage" data-label="AI" data-i18n-label="panel.ai"> |
| 587 | <div class="chead"> |
| 588 | <!-- The mark, shown only on a Diamond's crystal (see showCentre): the |
| 589 | crystal is a document you own rather than a conversation, and the |
| 590 | panel says which of its two faces is up. --> |
| 591 | <img class="chead-mark" id="chead-mark" src="assets/daimond_mark.svg" alt="" aria-hidden="true" style="display:none"> |
| 592 | <span class="ctitle" id="current-session-name" role="heading" aria-level="2">No chat</span> |
| 593 | <!-- A Diamond's two faces. The crystal is the daimon's output; the chat |
| 594 | is its conversation, which is persistent and was until now not shown |
| 595 | at all. Only drawn on a Diamond — a chat has one face. --> |
| 596 | <div class="dview" id="diamond-view" role="group" style="display:none" |
| 597 | data-i18n-aria-label="crystal.view_switch" aria-label="Which face of this Diamond"> |
| 598 | <button class="dview-btn" id="dview-crystal" type="button" aria-pressed="true" |
| 599 | data-i18n="crystal.view_crystal">Crystal</button> |
| 600 | <button class="dview-btn" id="dview-chat" type="button" aria-pressed="false" |
| 601 | data-i18n="crystal.view_chat">Chat</button> |
| 602 | </div> |
| 603 | <div class="chead-right"> |
| 604 | <span class="cmeter" id="ai-meter"></span> |
| 605 | <!-- The controls that were an icon nobody could read. A word says what a |
| 606 | picture of a spanner was only hinting at. --> |
| 607 | <span class="csel" id="select-tools" style="display:none"> |
| 608 | <button class="chip-btn" id="sel-all" data-i18n="chat.select_all">Select all</button> |
| 609 | <button class="chip-btn" id="sel-none" data-i18n="chat.deselect_all">Deselect all</button> |
| 610 | <button class="chip-btn accent" id="sel-fold" data-i18n="chat.fold_selected">Fold selected</button> |
| 611 | </span> |
| 612 | <!-- Which permission mode the machine hand is in, where the work is |
| 613 | actually watched. A mode you have to remember is one you will be |
| 614 | wrong about, and being wrong about this one matters — so it is a |
| 615 | word on screen beside the model, not a setting you go and look |
| 616 | up. The word IS the state; the accent is only so the eye lands. --> |
| 617 | <button class="chip-btn mode-chip" id="hand-mode-chip" |
| 618 | aria-haspopup="dialog" aria-expanded="false" aria-controls="hand-mode-pop" |
| 619 | title="Permission mode: what Daimond does without asking" |
| 620 | data-i18n-title="permmode.chip_help"><span class="mode-chip-dot" aria-hidden="true"></span><span class="mode-chip-txt" id="hand-mode-chip-txt">Guarded</span></button> |
| 621 | <!-- Concise: a standing toggle that puts the `/concise` skill in |
| 622 | front of every turn this chat sends. A skill, not a hidden |
| 623 | system-prompt tweak, so what it asks for is a file in the |
| 624 | workspace the user can read and edit. --> |
| 625 | <button class="chip-btn icon-chip" id="concise-chip" aria-pressed="false" style="display:none" |
| 626 | title="Ask for short answers in this chat. Every message carries the /concise skill, a file in your workspace you can edit." |
| 627 | aria-label="Concise" data-i18n-aria-label="chat.concise" |
| 628 | data-i18n-title="chat.concise_help"><svg class="ic" viewBox="0 0 24 24" |
| 629 | aria-hidden="true"><path d="M4 6h16M4 12h9M4 18h5"/></svg></button> |
| 630 | <!-- Fold, on the surface rather than only in the tile dialog. |
| 631 | Folding is done MID-FLOW -- when a thread has grown heavy, not |
| 632 | when you are configuring something -- and a control you reach |
| 633 | for mid-flow belongs where the flow is. Drawn on an ordinary |
| 634 | chat that has something in it, and NOT on a Diamond. This said |
| 635 | the opposite until 2026-08-20, and was the only place that said |
| 636 | it: a daimon folds itself before every round, and its engine's |
| 637 | session holds no messages for a hand fold to cut, so the one |
| 638 | conversation the button was drawn for was the one it could not |
| 639 | touch. --> |
| 640 | <button class="chip-btn" id="chat-fold-btn" style="display:none" |
| 641 | title="Fold this conversation now, keeping a summary of what came before." |
| 642 | data-i18n="chat.fold_now" data-i18n-title="chat.fold_now_help">Fold</button> |
| 643 | <!-- The whole conversation, out. Per-message copy has existed since the |
| 644 | thread did, and it answers a different question: this is for taking |
| 645 | a conversation somewhere else -- a bug report, a handover, another |
| 646 | model -- where copying forty messages one at a time is not a |
| 647 | workaround, it is a refusal. Drawn for a chat and for a Diamond's |
| 648 | chat face alike, because both are conversations. --> |
| 649 | <!-- What actually goes to the model. The thread is the payload laid out |
| 650 | once; this turns on the parts of it that are normally invisible — |
| 651 | the composed system message, whose it is, and the tool schemas |
| 652 | nobody has ever seen. --> |
| 653 | <!-- THE ONE WORD IN A ROW OF DRAWINGS. Copy, Steps and Concise are |
| 654 | verbs and draw as verbs; "wire" is not a picture of anything, and |
| 655 | the glyph it had -- five stacked rules -- is the same shape as the |
| 656 | Concise glyph beside it and read as a second Concise. The owner |
| 657 | asked for the word back, and only for this one. |
| 658 | `.label-chip` is what keeps the row's height: the word takes the |
| 659 | 26px box the icon chips set rather than the row growing to a |
| 660 | word's padding. See css/app.css. --> |
| 661 | <button class="chip-btn label-chip" id="wire-btn" aria-pressed="false" |
| 662 | title="Show what is actually sent to the model" |
| 663 | data-i18n="wire.chip" |
| 664 | data-i18n-title="wire.chip_help">Wire</button> |
| 665 | <button class="chip-btn icon-chip" id="chat-copy-btn" |
| 666 | title="Copy the whole conversation to the clipboard" |
| 667 | aria-label="Copy the conversation" data-i18n-aria-label="chat.copy_all_aria" |
| 668 | data-i18n-title="chat.copy_all_help"><svg class="ic" viewBox="0 0 24 24" |
| 669 | aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path |
| 670 | d="M5 15V5a2 2 0 012-2h8"/></svg></button> |
| 671 | <button class="chip-btn icon-chip" id="steps-toggle-btn" |
| 672 | title="Show or hide the tool steps in the thread" |
| 673 | aria-label="Tool steps" data-i18n-aria-label="chat.steps" |
| 674 | data-i18n-title="chat.steps_help"><svg class="ic" viewBox="0 0 24 24" |
| 675 | aria-hidden="true"><path d="M4 6h4v4H4zM10 8h10M4 14h4v4H4zM10 16h10"/></svg></button> |
| 676 | <button class="chip-btn" id="collapse-btn" title="Collapse every answer, leaving what you asked — and pick turns to fold" data-i18n-title="chat.collapse_help" aria-label="Collapse every answer" data-i18n-aria-label="chat.collapse">−</button> |
| 677 | <!-- Full screen, and only for the crystal face — `showCentre` |
| 678 | draws it there and nowhere else. The page a Diamond |
| 679 | renders can be an application, and an application inside |
| 680 | a panel inside an app reads on a phone as an app inside |
| 681 | an app; this takes the chrome away and leaves the page. |
| 682 | |
| 683 | It is LAST in the row so that it keeps its place when |
| 684 | everything else in the row is hidden: in full screen this |
| 685 | button is the only thing left of the header's right-hand |
| 686 | side, and it is the way back. Its words and its title are |
| 687 | set from `syncCrystalFullBtn` rather than written here, |
| 688 | because both change with the state and the label carries |
| 689 | a word as well as a glyph while the mode is on. --> |
| 690 | <button class="chip-btn cfull-btn" id="crystal-full-btn" type="button" |
| 691 | style="display:none" aria-pressed="false"> |
| 692 | <svg class="ic ic-cfull-in" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 4H4v5M15 4h5v5M15 20h5v-5M9 20H4v-5"/></svg> |
| 693 | <svg class="ic ic-cfull-out" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9h5V4M20 9h-5V4M20 15h-5v5M4 15h5v5"/></svg> |
| 694 | <span class="cfull-txt" id="crystal-full-txt"></span> |
| 695 | </button> |
| 696 | </div> |
| 697 | </div> |
| 698 | <div class="chat-output" id="chat-output"></div> |
| 699 | <!-- What a screen reader is told when an answer arrives. Said ONCE |
| 700 | per turn, not as the answer streams: a live region on the thread |
| 701 | itself would read every token mutation, which is a noise nobody |
| 702 | can leave switched on, and a live region people switch off is |
| 703 | worse than none because the app then looks answerable and is |
| 704 | not. Visually hidden — it says nothing a sighted reader cannot |
| 705 | already see. --> |
| 706 | <div class="vh" id="chat-say" role="status" aria-live="polite"></div> |
| 707 | <!-- An ordinary chat's WORKSPACE, and what it has put in front of the |
| 708 | model -- two groups, workspace first. See ATTACH_CONTRACT.md §5 and |
| 709 | `renderChatAttachments`. Built in JS, and drawn from the moment a |
| 710 | chat is in focus: the workspace group appears EMPTY, saying the chat |
| 711 | can reach nothing, because that is the state most worth saying and |
| 712 | the control that ends it lives in that group's own header. Hidden |
| 713 | here only until the first render, so the frame does not flash on a |
| 714 | page with no chat open. --> |
| 715 | <div class="arte-list chat-attach-strip" id="chat-attachments" style="display:none"></div> |
| 716 | <div class="chat-input-bar"> |
| 717 | <textarea id="chat-input" placeholder="Type a message…" rows="1" data-i18n-placeholder="chat.input_ph"></textarea> |
| 718 | <button id="chat-send" title="Send" data-i18n-title="chat.send" aria-label="Send" data-i18n-aria-label="chat.send">➤</button> |
| 719 | <!-- Drawn chevrons, not ↑ and ↓: a font's arrowhead is a few |
| 720 | pixels of ink on a 32px button, and read as specks beside |
| 721 | the stroked icons the rest of the app is made of. --> |
| 722 | <button id="chat-jump" title="Jump back to your last message. Press again to walk back through the ones before it." data-i18n-title="chat.jump_help" aria-label="Jump back to your last message" data-i18n-aria-label="chat.jump"> |
| 723 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 15l6-6 6 6"/></svg> |
| 724 | </button> |
| 725 | <button id="chat-end" title="Jump to the end of the chat." data-i18n-title="chat.end_help" aria-label="Jump to the end of the chat" data-i18n-aria-label="chat.end"> |
| 726 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg> |
| 727 | </button> |
| 728 | </div> |
| 729 | <!-- Crystal view: the Diamond command surface (steer + fold), shown in |
| 730 | place of the chat when a Diamond is selected. Built in JS. --> |
| 731 | <div class="crystal-view" id="crystal-view" style="display:none"> |
| 732 | <div class="crystal-body" id="crystal-body"></div> |
| 733 | <div class="crystal-controls" id="crystal-controls"></div> |
| 734 | </div> |
| 735 | </section> |
| 736 | |
| 737 | <!-- Web: a page. Shown always; operated only where a driver can reach |
| 738 | it — a same-origin page we serve ourselves, or a real tab under |
| 739 | Daimond Hands. A cross-origin iframe is opaque to us, and that is |
| 740 | the same-origin policy, not a gap to be worked around. --> |
| 741 | <aside class="panel web" id="panel-web" data-panel="web" data-zone="stage" data-label="Web" data-i18n-label="panel.web"> |
| 742 | <div class="chead"> |
| 743 | <span class="web-mode" id="web-mode" title="Who is driving" data-i18n-title="web.who_drives"></span> |
| 744 | <span class="ctitle web-url" id="web-url" role="heading" aria-level="2">The Web panel</span> |
| 745 | <div class="chead-right"> |
| 746 | <!-- `root/web`'s control. A slot, not a drawing: the widget is built |
| 747 | by `mountPause` in js/daimond.js, exactly as the rail's global |
| 748 | control is, so the app cannot grow a second picture of one |
| 749 | state. FIRST in the row, ahead of the page buttons, because it |
| 750 | is a statement about what this panel may SPEND rather than |
| 751 | another thing to do to the page — and because the refusal |
| 752 | sends you here to find it. --> |
| 753 | <span class="pptw-slot" id="web-pause"></span> |
| 754 | <button class="icon-btn" id="web-back" title="Back" data-i18n-title="web.back" aria-label="Back" data-i18n-aria-label="web.back">‹</button> |
| 755 | <button class="icon-btn" id="web-reload" title="Reload" data-i18n-title="web.reload" aria-label="Reload" data-i18n-aria-label="web.reload">⟳</button> |
| 756 | <button class="icon-btn" id="web-pop" title="Open in a real tab" data-i18n-title="web.pop_out" aria-label="Open in a real tab" data-i18n-aria-label="web.pop_out">↗</button> |
| 757 | <button class="addbtn panel-close" data-close="web" title="Close panel" data-i18n-title="common.close_panel"> |
| 758 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 759 | </button> |
| 760 | </div> |
| 761 | </div> |
| 762 | <div class="web-body" id="web-body"> |
| 763 | <!-- WHY THERE ARE TWO OF THEM. With Daimond Hands installed the page |
| 764 | is a real browser tab -- that is the only way it can carry the |
| 765 | user's own sign-ins and the only way it can be driven at all -- |
| 766 | and this panel shows a picture of that tab. The app has always |
| 767 | said so, in `web.opening`, and then wiped it the moment the page |
| 768 | arrived (`adoptPage` calls `note('')`): the sentence was on |
| 769 | screen only while the user was waiting, and gone by the time they |
| 770 | were looking at a window and a panel at once and wondering why. |
| 771 | A STRIP, not the `.web-note` overlay, for exactly that reason -- |
| 772 | an overlay cannot stay up over a live picture. Same shape as |
| 773 | `.web-readonly`, which is this panel's other standing |
| 774 | disclosure. --> |
| 775 | <div class="web-live" id="web-live" hidden></div> |
| 776 | <!-- A frame is a Tab stop, and an untitled one is announced as |
| 777 | "frame" and nothing else. --> |
| 778 | <iframe class="web-frame" id="web-frame" title="Web" data-i18n-title="panel.web" referrerpolicy="no-referrer" |
| 779 | sandbox="allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"></iframe> |
| 780 | <img class="web-mirror" id="web-mirror" alt="" style="display:none"> |
| 781 | <div class="web-note" id="web-note"></div> |
| 782 | <!-- The blindfold. Not a promise to look away: nothing is sent. --> |
| 783 | <div class="web-blind" id="web-blind" style="display:none"> |
| 784 | <div class="web-blind-eye">◍</div> |
| 785 | <div class="web-blind-title" data-i18n="web.blind_title">You're driving. I'm not watching.</div> |
| 786 | <div class="web-blind-note" data-i18n-html="web.blind_note">Daimond has stopped reading this page — no text, no picture, no keystrokes. Sign in, then click <b>Resume Daimond</b> in the browser tab to hand the wheel back. That button is in the tab, not here, so this page can never take the wheel from you.</div> |
| 787 | <button class="web-takeover" id="web-takeover" data-i18n="web.resumed">I’ve resumed, check</button> |
| 788 | </div> |
| 789 | </div> |
| 790 | </aside> |
| 791 | |
| 792 | <!-- The document itself: the file, where there is room to read it. |
| 793 | What it LOOKS like when it is typeset lives in the Preview panel |
| 794 | below, because a source and its pages are two things to see at |
| 795 | once and not two turns at one panel. --> |
| 796 | <aside class="panel doc" id="panel-doc" data-panel="doc" data-zone="stage" data-label="Doc" data-i18n-label="panel.doc"> |
| 797 | <div class="chead"> |
| 798 | <span class="ctitle doc-name" id="doc-name" role="heading" aria-level="2"></span> |
| 799 | <div class="chead-right"> |
| 800 | <!-- Line numbers, in the header rather than in the toolbar the |
| 801 | text view draws for itself: it is a way of reading the |
| 802 | panel, not a thing to do to the file, and it is asked for |
| 803 | while looking at the document. Shown only while a text file |
| 804 | is up, since a PDF has no lines to number. --> |
| 805 | <button class="icon-btn" id="doc-lineno" title="Line numbers" data-i18n-title="files.line_numbers" aria-label="Line numbers" data-i18n-aria-label="files.line_numbers" aria-pressed="false" style="display:none"> |
| 806 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h2M4 12h2M4 18h2M10 6h10M10 12h10M10 18h10"/></svg> |
| 807 | </button> |
| 808 | <button class="addbtn panel-close" data-close="doc" title="Close panel" data-i18n-title="common.close_panel"> |
| 809 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 810 | </button> |
| 811 | </div> |
| 812 | </div> |
| 813 | <!-- The file as characters: read, or edited. Anything that is not |
| 814 | characters is not this panel's — see #panel-preview. --> |
| 815 | <div class="doc-view" id="doc-view"></div> |
| 816 | </aside> |
| 817 | |
| 818 | <!-- What a document LOOKS like. |
| 819 | Three renderings, one at a time, and all three are the same thing: |
| 820 | a compiled PDF in the <embed>, a file the browser or the viewer |
| 821 | draws in #pv-view, and — standing in front of both while a .typ is |
| 822 | being watched — the live typeset pages typstwatch.js mounts here. |
| 823 | It is a SEPARATE PANEL from the document because the whole point is |
| 824 | to see the source and its pages at once; while they shared one, the |
| 825 | two took turns and the loop had to decide whose turn it was. --> |
| 826 | <aside class="panel doc preview" id="panel-preview" data-panel="preview" data-zone="stage" data-label="Preview" data-i18n-label="panel.preview"> |
| 827 | <div class="chead"> |
| 828 | <span class="ctitle doc-name" id="pv-name" role="heading" aria-level="2"></span> |
| 829 | <div class="chead-right"> |
| 830 | <button class="addbtn panel-close" data-close="preview" title="Close panel" data-i18n-title="common.close_panel"> |
| 831 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 832 | </button> |
| 833 | </div> |
| 834 | </div> |
| 835 | <div class="doc-view" id="pv-view"></div> |
| 836 | <embed class="doc-embed" id="doc-embed" type="application/pdf" style="display:none"> |
| 837 | </aside> |
| 838 | |
| 839 | <!-- One mail message, where there is room to read it — and, now that |
| 840 | the stage seats two, with the chat still beside it. --> |
| 841 | <aside class="panel msg" id="panel-msg" data-panel="msg" data-zone="stage" data-label="Email message" data-i18n-label="panel.msg"> |
| 842 | <div class="chead"> |
| 843 | <span class="ctitle" id="msg-title" role="heading" aria-level="2">Message</span> |
| 844 | <div class="chead-right"> |
| 845 | <button class="addbtn panel-close" data-close="msg" title="Close panel" data-i18n-title="common.close_panel"> |
| 846 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 847 | </button> |
| 848 | </div> |
| 849 | </div> |
| 850 | <div class="msg-head" id="msg-head"></div> |
| 851 | <div class="msg-body" id="msg-body"></div> |
| 852 | </aside> |
| 853 | |
| 854 | <!-- What Daimond can do, and what the rest would cost. A stage guest, because a |
| 855 | tool with a price on it is a thing being inspected, and the rail is too |
| 856 | narrow to inspect anything in. --> |
| 857 | <aside class="panel tools" id="panel-tools" data-panel="tools" data-zone="stage" data-label="Tools" data-i18n-label="panel.tools"> |
| 858 | <div class="chead"> |
| 859 | <span class="ctitle" data-i18n="panel.tools" role="heading" aria-level="2">Tools</span> |
| 860 | <div class="chead-right"> |
| 861 | <button class="addbtn panel-close" data-close="tools" title="Close panel" data-i18n-title="common.close_panel"> |
| 862 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 863 | </button> |
| 864 | </div> |
| 865 | </div> |
| 866 | <div class="tools-body" id="tools-body"></div> |
| 867 | </aside> |
| 868 | |
| 869 | <!-- Writing one. A stage guest, not a popup: a message worth writing is worth |
| 870 | the room, and the daimon stays beside it, so the draft can be talked over |
| 871 | while it is being written. Nothing here sends by itself — the Send button |
| 872 | is the only thing in Daimond that puts mail on the wire, and only a person |
| 873 | can press it. --> |
| 874 | <aside class="panel compose" id="panel-compose" data-panel="compose" data-zone="stage" data-label="Compose" data-i18n-label="panel.compose"> |
| 875 | <div class="chead"> |
| 876 | <span class="ctitle" id="compose-title" role="heading" aria-level="2">New message</span> |
| 877 | <div class="chead-right"> |
| 878 | <button class="addbtn panel-close" data-close="compose" title="Close panel" data-i18n-title="common.close_panel"> |
| 879 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 880 | </button> |
| 881 | </div> |
| 882 | </div> |
| 883 | <div class="compose-head"> |
| 884 | <label class="compose-row"><span data-i18n="compose.from">From</span><select id="compose-from"></select></label> |
| 885 | <label class="compose-row"><span data-i18n="compose.to">To</span><input id="compose-to" type="text" autocomplete="off" placeholder="someone@example.com"></label> |
| 886 | <label class="compose-row"><span data-i18n="compose.cc">Cc</span><input id="compose-cc" type="text" autocomplete="off" placeholder="Optional" data-i18n-placeholder="compose.optional"></label> |
| 887 | <label class="compose-row"><span data-i18n="compose.subject">Subject</span><input id="compose-subject" type="text" autocomplete="off"></label> |
| 888 | </div> |
| 889 | <textarea class="compose-text" id="compose-text" spellcheck="true" placeholder="Write your message." data-i18n-placeholder="compose.body_ph"></textarea> |
| 890 | <div class="compose-atts" id="compose-atts"></div> |
| 891 | <div class="compose-foot"> |
| 892 | <button class="compose-send" id="compose-send" data-i18n="compose.send">Send</button> |
| 893 | <button class="compose-btn" id="compose-save" data-i18n="compose.save_draft">Save draft</button> |
| 894 | <button class="compose-btn" id="compose-attach" data-i18n="compose.attach">Attach…</button> |
| 895 | <input type="file" id="compose-file" multiple hidden> |
| 896 | <button class="compose-btn danger" id="compose-discard" data-i18n="compose.discard">Discard</button> |
| 897 | <span class="compose-note" id="compose-note"></span> |
| 898 | </div> |
| 899 | </aside> |
| 900 | |
| 901 | <!-- The graph: the Diamonds and the links between them, drawn as one |
| 902 | still, layered picture. A stage guest because it is an instrument |
| 903 | for checking the association structure, and checking needs room. --> |
| 904 | <aside class="panel graph" id="panel-graph" data-panel="graph" data-zone="stage" data-label="Graph" data-i18n-label="panel.graph"> |
| 905 | <div class="chead"> |
| 906 | <span class="ctitle" data-i18n="panel.graph" role="heading" aria-level="2">Graph</span> |
| 907 | <div class="chead-right"> |
| 908 | <button class="addbtn panel-close" data-close="graph" title="Close panel" data-i18n-title="common.close_panel"> |
| 909 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 910 | </button> |
| 911 | </div> |
| 912 | </div> |
| 913 | <div class="graph-body" id="graph-body"></div> |
| 914 | </aside> |
| 915 | |
| 916 | <!-- Terminal: a real terminal on the user's machine, reached through the |
| 917 | machine hand. Everything inside #termp-host is built by js/terminal.js |
| 918 | on the first open, never at load: a canvas, its screen model and its |
| 919 | screen-reader mirror are not worth building for a panel nobody has |
| 920 | asked for. |
| 921 | |
| 922 | A stage guest, and for the same reason the Web panel is one: it is a |
| 923 | thing you work IN rather than glance at, and it wants the room and the |
| 924 | height to be worked in. It shipped in the dock for a day and the dock |
| 925 | is the wrong shape for it twice over -- the dock's own grid seats four |
| 926 | below 1900px, so a fifth panel there could only arrive by closing one |
| 927 | of the four that were already earning their place. |
| 928 | |
| 929 | The class is `termpanel` and not `term` on purpose. `.term` is the |
| 930 | class terminal.js puts on the thing it builds, and a panel wearing it |
| 931 | would inherit the terminal's own background, border and 100% height. --> |
| 932 | <aside class="panel termpanel" id="panel-term" data-panel="term" data-zone="stage" data-label="Terminal" data-i18n-label="panel.term"> |
| 933 | <div class="chead"> |
| 934 | <span class="ctitle" role="heading" aria-level="2" data-i18n="panel.term">Terminal</span> |
| 935 | <!-- What the program calls itself (OSC 0 and 2). aria-hidden, because |
| 936 | the same words reach a screen reader through the terminal's own |
| 937 | live region: a title that changes on every prompt would otherwise |
| 938 | interrupt the output being read out. --> |
| 939 | <span class="termp-title" id="termp-title" aria-hidden="true"></span> |
| 940 | <div class="chead-right"> |
| 941 | <!-- A bell rung while the eye is elsewhere. The terminal flashes its |
| 942 | own edge; this is the same event where the panel can be seen. --> |
| 943 | <span class="termp-bell" id="termp-bell" aria-hidden="true"></span> |
| 944 | <button class="addbtn" data-act="term-start" title="Start a terminal" aria-label="Start a terminal" data-i18n-title="term.start" data-i18n-aria-label="term.start"> |
| 945 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5l11 7-11 7z"/></svg> |
| 946 | </button> |
| 947 | <button class="addbtn" data-act="term-stop" title="Stop the program" aria-label="Stop the program" data-i18n-title="term.stop" data-i18n-aria-label="term.stop"> |
| 948 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><rect x="6.5" y="6.5" width="11" height="11" rx="1.5"/></svg> |
| 949 | </button> |
| 950 | <button class="addbtn panel-close" data-close="term" title="Close panel" data-i18n-title="common.close_panel"> |
| 951 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 952 | </button> |
| 953 | </div> |
| 954 | </div> |
| 955 | <div class="termp-card"> |
| 956 | <!-- Where the terminal is, in a sentence: starting, running, refused, |
| 957 | finished. A refusal from the hand or the relay is shown VERBATIM |
| 958 | — those sentences are written to be acted on, and a paraphrase |
| 959 | loses the only instruction the reader gets. --> |
| 960 | <p class="termp-state" id="termp-state" role="status"></p> |
| 961 | <!-- Missing output, said BESIDE the stream and never written into it: |
| 962 | bytes written into a terminal are drawn, so a marker in the stream |
| 963 | would be damage on top of the loss. See handpty.js, "A hole is |
| 964 | shown, not stitched". --> |
| 965 | <div class="termp-host" id="termp-host"> |
| 966 | <div class="termp-gaps" id="termp-gaps" role="log" aria-live="polite" aria-label="Notices about this terminal" data-i18n-aria-label="term.notices_label"></div> |
| 967 | </div> |
| 968 | <!-- The way back out. A terminal takes nearly every key, so the one |
| 969 | that leaves it has to be said in writing rather than guessed at. --> |
| 970 | <!-- Selection, copy and paste were BUILT and invisible: drag, double-click, |
| 971 | triple-click, Ctrl-Shift-C, Ctrl-Shift-V and Ctrl-Shift-A have all worked |
| 972 | since the panel shipped, and the only line naming them was `term.hint`, |
| 973 | rendered screen-reader-only. The owner reported copy and paste as a |
| 974 | missing feature on 2026-08-26. It is the same string, in every locale |
| 975 | already, put where a sighted reader meets it. --> |
| 976 | <p class="termp-foot" id="termp-keys" data-i18n="term.hint">Type to send keys straight to the program. With text selected, Ctrl-C copies it; with none, Ctrl-C interrupts the program. Ctrl-V pastes, Ctrl-Shift-A selects everything, and right-click offers the same three. Shift with Page Up or Page Down moves through what has scrolled past.</p> |
| 977 | <p class="termp-foot" id="termp-foot" data-i18n="term.leave_hint">Press F6 to move the keyboard out of the terminal.</p> |
| 978 | </div> |
| 979 | </aside> |
| 980 | </main> |
| 981 | |
| 982 | <div class="phandle" id="handle-dock" title="Drag to resize, double-click to reset" data-i18n-title="layout.handle"></div> |
| 983 | <!-- ── The dock ──────────────────────────────────────────────── |
| 984 | The right zone: the sources you pull from. A closed dock panel is a |
| 985 | tag in the header; clicking the tag docks it. A second column opens |
| 986 | only when there is genuinely room for one. --> |
| 987 | <div class="dock" id="dock"> |
| 988 | <div class="pcol" id="dock-a"></div> |
| 989 | <div class="pcol" id="dock-b"></div> |
| 990 | </div> |
| 991 | |
| 992 | <!-- Dock panels below are relocated into their column by the layout engine. --> |
| 993 | |
| 994 | <!-- Agents: live runs --> |
| 995 | <!-- Pending: what a daimon has proposed and is waiting on you for. |
| 996 | Notes2: "we have the user approving all outgoing email, so this leads |
| 997 | to the need for a new Dock panel, say 'Pending'". Every tile carries |
| 998 | one Diamond, a priority the daimon set, a headline that expands, and |
| 999 | the three answers: do it, discuss it, drop it. --> |
| 1000 | <aside class="panel pending" id="panel-pending" data-panel="pending" data-zone="dock" data-label="Pending" data-i18n-label="panel.pending"> |
| 1001 | <div class="railhead"><span role="heading" aria-level="2" data-i18n="panel.pending">Pending</span> |
| 1002 | <span class="rail-count" id="pending-count"></span> |
| 1003 | <span class="pending-actions"> |
| 1004 | <select class="pending-sort" id="pending-sort" data-i18n-title="pending.sort" title="Sort"> |
| 1005 | <option value="priority" data-i18n="pending.by_priority">By priority</option> |
| 1006 | <option value="newest" data-i18n="pending.by_newest">Newest first</option> |
| 1007 | <option value="oldest" data-i18n="pending.by_oldest">Oldest first</option> |
| 1008 | </select> |
| 1009 | <button class="addbtn panel-close" data-close="pending" title="Close panel" data-i18n-title="common.close_panel"> |
| 1010 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 1011 | </button> |
| 1012 | </span> |
| 1013 | </div> |
| 1014 | <div class="pending-list" id="pending-list"></div> |
| 1015 | </aside> |
| 1016 | |
| 1017 | <aside class="panel agents" id="panel-agents" data-panel="agents" data-zone="dock" data-label="Agents" data-i18n-label="panel.agents"> |
| 1018 | <div class="railhead"><span role="heading" aria-level="2" data-i18n="panel.agents">Agents</span> |
| 1019 | <span class="rail-count" id="agents-count"></span> |
| 1020 | <!-- Pause / resume / stop, acting on every agent at once. Pause holds |
| 1021 | the pool and hangs up what is running (keeping its work); resume |
| 1022 | continues; stop is the kill switch. The same three ride on each |
| 1023 | tile. This is the cost brake for a runaway fan-out. --> |
| 1024 | <div class="agents-ctl" id="agents-ctl" style="display:none" role="group" aria-label="Control all agents" data-i18n-aria-label="agents.control_all"> |
| 1025 | <button class="agc" id="agents-pause" title="Pause all agents" aria-label="Pause all agents" data-i18n-title="agents.pause_all" data-i18n-aria-label="agents.pause_all"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="6" width="3.4" height="12" rx="1"/><rect x="13.6" y="6" width="3.4" height="12" rx="1"/></svg></button> |
| 1026 | <button class="agc" id="agents-play" title="Resume all paused agents" aria-label="Resume all paused agents" data-i18n-title="agents.resume_all" data-i18n-aria-label="agents.resume_all"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5l11 7-11 7z"/></svg></button> |
| 1027 | <button class="agc" id="agents-stop" title="Stop all agents" aria-label="Stop all agents" data-i18n-title="agents.stop_all" data-i18n-aria-label="agents.stop_all"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button> |
| 1028 | </div> |
| 1029 | <button class="addbtn" id="agents-clear" title="Clear finished agents" style="display:none" data-i18n-title="agents.clear_finished"> |
| 1030 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M9 7V5h6v2M7 7l1 12h8l1-12"/></svg> |
| 1031 | </button> |
| 1032 | <button class="addbtn panel-close" data-close="agents" title="Close panel" data-i18n-title="common.close_panel"> |
| 1033 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 1034 | </button> |
| 1035 | </div> |
| 1036 | <!-- How many agents are running, paused and queued: the fan-out's cost made legible. --> |
| 1037 | <div class="agents-stat" id="agents-stat" style="display:none"></div> |
| 1038 | <!-- Find a run among many by name, task, Diamond or tag. A flat list |
| 1039 | with chips, not a Diamond-and-children tree: the parent a run |
| 1040 | belongs to rides on the run as a chip, so the tree's height is |
| 1041 | not spent on it. --> |
| 1042 | <div class="diamond-find agent-find"> |
| 1043 | <input type="search" class="diamond-search" id="agent-search" placeholder="Search agents" aria-label="Search agents by name, task, Diamond or tag" autocomplete="off" data-i18n-placeholder="agents.search" data-i18n-aria-label="agents.search_help"> |
| 1044 | <div class="diamond-filter" id="agent-filter" style="display:none"></div> |
| 1045 | </div> |
| 1046 | <div class="agents-list" id="agents-list"></div> |
| 1047 | </aside> |
| 1048 | |
| 1049 | <!-- Email: the mailboxes, and what is in them --> |
| 1050 | <aside class="panel mail" id="panel-mail" data-panel="mail" data-zone="dock" data-label="Email" data-i18n-label="panel.mail"> |
| 1051 | <div class="railhead"><span role="heading" aria-level="2" data-i18n="panel.mail">Email</span> |
| 1052 | <span class="mail-actions"> |
| 1053 | <button class="addbtn" data-act="mail-new" title="Write a message" aria-label="Write a message" data-i18n-title="mail.write" data-i18n-aria-label="mail.write"> |
| 1054 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 013 3L7 19l-4 1 1-4z"/></svg> |
| 1055 | </button> |
| 1056 | <button class="addbtn" data-act="mail-add" title="Add a mailbox" aria-label="Add a mailbox" data-i18n-title="mail.add_mailbox" data-i18n-aria-label="mail.add_mailbox"> |
| 1057 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg> |
| 1058 | </button> |
| 1059 | <button class="addbtn" data-act="mail-sync" title="Sync now" aria-label="Sync now" data-i18n-title="mail.sync_now" data-i18n-aria-label="mail.sync_now"> |
| 1060 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 10-.6 4"/><path d="M20 4v5h-5"/></svg> |
| 1061 | </button> |
| 1062 | <button class="addbtn panel-close" data-close="mail" title="Close panel" data-i18n-title="common.close_panel"> |
| 1063 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 1064 | </button> |
| 1065 | </span> |
| 1066 | </div> |
| 1067 | <div class="mail-card"> |
| 1068 | <div class="mail-state" id="mail-state"></div> |
| 1069 | <div class="mail-accounts" id="mail-accounts"></div> |
| 1070 | <!-- Which of the account's folders the list below is showing. |
| 1071 | Empty until an account exists to have folders. --> |
| 1072 | <div class="mail-folders" id="mail-folders"></div> |
| 1073 | <div class="mail-list" id="mail-list"></div> |
| 1074 | </div> |
| 1075 | </aside> |
| 1076 | |
| 1077 | <!-- Workspace: OPFS file tree --> |
| 1078 | <aside class="panel work" id="panel-work" data-panel="work" data-zone="dock" data-label="Workspace" data-i18n-label="panel.work"> |
| 1079 | <div class="railhead"><span role="heading" aria-level="2" data-i18n="panel.work">Workspace</span> |
| 1080 | <span class="files-actions"> |
| 1081 | <button class="addbtn" data-act="new-file" title="New file" aria-label="New file" data-i18n-title="work.new_file" data-i18n-aria-label="work.new_file"> |
| 1082 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M13 3H6a2 2 0 00-2 2v14a2 2 0 002 2h12a2 2 0 002-2V10z"/><path d="M13 3v6h6"/><path d="M12 12.5v5M9.5 15h5"/></svg> |
| 1083 | </button> |
| 1084 | <button class="addbtn" data-act="new-dir" title="New folder" aria-label="New folder" data-i18n-title="work.new_folder" data-i18n-aria-label="work.new_folder"> |
| 1085 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><path d="M12 11v5M9.5 13.5h5"/></svg> |
| 1086 | </button> |
| 1087 | <button class="addbtn" data-act="upload" title="Upload files" aria-label="Upload files" data-i18n-title="work.upload" data-i18n-aria-label="work.upload"> |
| 1088 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 16V4M8 8l4-4 4 4"/><path d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2"/></svg> |
| 1089 | </button> |
| 1090 | <button class="addbtn" data-act="refresh" title="Refresh" aria-label="Refresh" data-i18n-title="common.refresh" data-i18n-aria-label="common.refresh"> |
| 1091 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 10-.6 4"/><path d="M20 4v5h-5"/></svg> |
| 1092 | </button> |
| 1093 | <button class="addbtn panel-close" data-close="work" title="Close panel" data-i18n-title="common.close_panel"> |
| 1094 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 1095 | </button> |
| 1096 | </span> |
| 1097 | </div> |
| 1098 | <div class="files-card"> |
| 1099 | <div class="files-mode"></div> |
| 1100 | <button class="instructions-chip" id="instructions-chip" style="display:none"></button> |
| 1101 | <div class="files-path"></div> |
| 1102 | <div class="files-filter"><input type="text" class="files-filter-input" placeholder="Filter…" autocomplete="off" spellcheck="false" data-i18n-placeholder="work.filter_ph"></div> |
| 1103 | <div class="files-tree"></div> |
| 1104 | <!-- Daimond's own store, which the tree above deliberately hides: a × |
| 1105 | beside `diamonds/` would delete every Diamond you have. Hiding it |
| 1106 | answered the wrong question — what was wanted was to SEE it. Its |
| 1107 | own section because the two resolve against different roots, and a |
| 1108 | single tree quietly spanning both is the confusion |
| 1109 | dev/ROOT_SEPARATION.md exists to prevent. --> |
| 1110 | <div class="sys-sec" id="sys-sec"> |
| 1111 | <!-- Not a `<details>`. Chrome leaves closed details content with a |
| 1112 | stale layout box that reports a size while refusing focus, so an |
| 1113 | accessibility check sees rows that look on screen and cannot be |
| 1114 | reached — and the fix for that is not more CSS, it is the |
| 1115 | disclosure the rest of this app already uses. --> |
| 1116 | <button class="sys-head" id="sys-head" type="button" aria-expanded="false" aria-controls="sys-body" data-i18n="sys.head">System</button> |
| 1117 | <div class="sys-body" id="sys-body" hidden> |
| 1118 | <div class="sys-note" data-i18n="sys.note">Daimond’s own files. They live in the browser, not in the folder you have open, and they travel between your devices.</div> |
| 1119 | <div class="sys-path" id="sys-path"></div> |
| 1120 | <div class="sys-tree" id="sys-tree"></div> |
| 1121 | </div> |
| 1122 | </div> |
| 1123 | </div> |
| 1124 | </aside> |
| 1125 | |
| 1126 | <!-- Spending: where inference (your key) and credits (gateway services) go --> |
| 1127 | <aside class="panel spend" id="panel-spend" data-panel="spend" data-zone="dock" data-label="Spending" data-i18n-label="panel.spend"> |
| 1128 | <div class="railhead"><span role="heading" aria-level="2" data-i18n="panel.spend">Spending</span> |
| 1129 | <span class="spend-actions"> |
| 1130 | <button class="addbtn" data-act="spend-refresh" title="Refresh" aria-label="Refresh" data-i18n-title="common.refresh" data-i18n-aria-label="common.refresh"> |
| 1131 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 10-.6 4"/><path d="M20 4v5h-5"/></svg> |
| 1132 | </button> |
| 1133 | <button class="addbtn panel-close" data-close="spend" title="Close panel" data-i18n-title="common.close_panel"> |
| 1134 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 1135 | </button> |
| 1136 | </span> |
| 1137 | </div> |
| 1138 | <div class="spend-view" id="spend-view"></div> |
| 1139 | </aside> |
| 1140 | |
| 1141 | <!-- Trash: what has been deleted, and is not gone. Deleting a chat or a |
| 1142 | Diamond moves it here and asks nothing; the two questions in this app |
| 1143 | that cannot be taken back are both in this panel. See js/trash.js. --> |
| 1144 | <aside class="panel trash" id="panel-trash" data-panel="trash" data-zone="dock" data-label="Trash" data-i18n-label="panel.trash"> |
| 1145 | <div class="railhead"><span role="heading" aria-level="2" data-i18n="panel.trash">Trash</span> |
| 1146 | <span class="rail-count" id="trash-count"></span> |
| 1147 | <span class="trash-actions"> |
| 1148 | <button class="addbtn" id="trash-restore-all" data-act="trash-restore-all" title="Restore everything" aria-label="Restore everything" data-i18n-title="trash.restore_all" data-i18n-aria-label="trash.restore_all"> |
| 1149 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 12a8 8 0 10 2.5-5.8"/><path d="M4 4v5h5"/></svg> |
| 1150 | </button> |
| 1151 | <button class="addbtn danger" id="trash-empty" data-act="trash-empty" title="Empty trash" aria-label="Empty trash" data-i18n-title="trash.empty" data-i18n-aria-label="trash.empty"> |
| 1152 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M9 7V5h6v2M7 7l1 12h8l1-12"/></svg> |
| 1153 | </button> |
| 1154 | <button class="addbtn panel-close" data-close="trash" title="Close panel" data-i18n-title="common.close_panel"> |
| 1155 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 1156 | </button> |
| 1157 | </span> |
| 1158 | </div> |
| 1159 | <!-- What the trash is holding, in bytes, and for how long it holds it. |
| 1160 | A trash that grows in silence is how storage fills with things |
| 1161 | somebody believes they deleted. --> |
| 1162 | <div class="trash-note" id="trash-note"></div> |
| 1163 | <div class="trash-list arte-list" id="trash-list"></div> |
| 1164 | </aside> |
| 1165 | |
| 1166 | <!-- Social: where a note about Daimond is written, where the proposals |
| 1167 | made from notes are read and voted on, and where messages and the |
| 1168 | people they come from will live. A note stays on this device until |
| 1169 | somebody presses Send on that one note, and what leaves is exactly |
| 1170 | the characters on the screen. See js/improve.js and |
| 1171 | dev/IMPROVE_CONTRACT.md. |
| 1172 | |
| 1173 | FIVE CHIPS, and two of them lead somewhere that is not built yet -- |
| 1174 | which those two views SAY, rather than drawing an empty list. An |
| 1175 | empty list is the sentence "nothing has arrived", and that is a |
| 1176 | different claim from "this is not switched on". `#social-messages` |
| 1177 | and `#social-people` are the containers the messaging and people |
| 1178 | lanes render into; each carries its own empty line, which those |
| 1179 | lanes hide once they have drawn a row. --> |
| 1180 | <aside class="panel social" id="panel-social" data-panel="social" data-zone="dock" data-label="Social" data-i18n-label="panel.social"> |
| 1181 | <div class="railhead"><span role="heading" aria-level="2" data-i18n="panel.social">Social</span> |
| 1182 | <span class="imp-chips"> |
| 1183 | <button type="button" class="imp-chip" data-view="messages" aria-pressed="false" data-i18n="social.messages">Messages</button> |
| 1184 | <button type="button" class="imp-chip" data-view="people" aria-pressed="false" data-i18n="social.people">People</button> |
| 1185 | <!-- SHARE, and it is here because the module behind it was complete |
| 1186 | and unreachable. js/share.js could compose a Diamond, seal it to |
| 1187 | one person, choose a carrier, write a .dshare out and read one |
| 1188 | back in — and no button in the app called any of it. That is |
| 1189 | this project's signature failure and it had shipped three times |
| 1190 | before this one. Beside People because a share goes TO somebody, |
| 1191 | and the same projection names them in both. --> |
| 1192 | <button type="button" class="imp-chip" data-view="share" aria-pressed="false" data-i18n="social.share">Share</button> |
| 1193 | <button type="button" class="imp-chip on" data-view="proposals" aria-pressed="true" data-i18n="social.proposals">Proposals</button> |
| 1194 | <button type="button" class="imp-chip" data-view="settings" aria-pressed="false" data-i18n="social.settings">Settings</button> |
| 1195 | <!-- What this panel is, in the guide. The one circled "i" this app |
| 1196 | draws -- the same path as the top bar's About button, because |
| 1197 | two information marks drawn differently are two marks the |
| 1198 | reader has to learn separately. It opens The interface, which |
| 1199 | is the page that names every region of the window; the header |
| 1200 | "?" beside About opens the guide's front page, and this is the |
| 1201 | same route (`DaimondWeb.guide`) asked for one page of it. |
| 1202 | |
| 1203 | `.icon-btn` and NOT `.addbtn`, which is what the closer beside |
| 1204 | it wears. An `.addbtn` is a glyph on a small PLATE, and in the |
| 1205 | warm skin that plate's corner is half its side -- a circle. A |
| 1206 | circled "i" inside it drew two concentric rings, magnified 6x |
| 1207 | and read side by side against the top bar's own mark. The mark |
| 1208 | brings its own circle, so it is given no second one, which is |
| 1209 | exactly how About and the guide "?" are drawn. --> |
| 1210 | </span> |
| 1211 | <!-- THE HEAD'S OWN CONTROLS, OUT OF THE CHIP GROUP. They were inside |
| 1212 | `.imp-chips`, which wraps, so on a 390px dock they wrapped with |
| 1213 | it: the closer sat 248px left of the panel's right edge and 47px |
| 1214 | down, alone at the FOOT of the head, while every other panel in |
| 1215 | the app holds its cross within 15px of the top-right corner. |
| 1216 | Measured 2026-08-28; the census is in dev/lane_closer_audit.mjs. |
| 1217 | Ordered ahead of the chips (see app.css) so this row is the head's |
| 1218 | first line and the chips take the second. --> |
| 1219 | <span class="railhead-acts"> |
| 1220 | <button type="button" class="icon-btn imp-info" id="social-info" |
| 1221 | title="What this panel is, in the guide" data-i18n-title="social.info" |
| 1222 | aria-label="What this panel is, in the guide" data-i18n-aria-label="social.info"> |
| 1223 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="7.4" r="1.2" fill="currentColor" stroke="none"/><path d="M12 11.6v5"/></svg> |
| 1224 | </button> |
| 1225 | <button class="addbtn panel-close" data-close="social" title="Close panel" data-i18n-title="common.close_panel"> |
| 1226 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 1227 | </button> |
| 1228 | </span> |
| 1229 | </div> |
| 1230 | |
| 1231 | <!-- Messages, and the people they come from. Empty shells with an |
| 1232 | honest line in each: js/post.js fills the first, js/trust.js the |
| 1233 | second, and both hide the line by calling |
| 1234 | `DaimondSocial.filled(view, n)` once they have drawn a row. |
| 1235 | |
| 1236 | EACH LIST IS THAT LANE'S OWN REGION and is cleared wholesale on |
| 1237 | every redraw, so nothing belonging to anything else may be parked |
| 1238 | inside one. The head, the chips and the empty line above are this |
| 1239 | panel's; everything below the line is theirs. --> |
| 1240 | <div class="imp-view" id="social-messages" hidden> |
| 1241 | <div class="rail-note imp-off" id="social-messages-off" data-i18n="social.messages_off">Messages are not switched on in this build.</div> |
| 1242 | <!-- js/post.js OWNS the inside of this list and clears it on every |
| 1243 | redraw, so nothing else may keep a node in here. --> |
| 1244 | <div class="imp-list" id="social-messages-list"></div> |
| 1245 | </div> |
| 1246 | |
| 1247 | <div class="imp-view" id="social-people" hidden> |
| 1248 | <div class="rail-note imp-off" id="social-people-off" data-i18n="social.people_off">Nobody yet. Show your code to somebody in the room, or read theirs.</div> |
| 1249 | <div class="imp-list" id="social-people-list"></div> |
| 1250 | </div> |
| 1251 | |
| 1252 | <!-- js/share.js OWNS the inside of this list and clears it on every |
| 1253 | redraw, exactly as post.js owns the messages list above. The head, |
| 1254 | the chip and the empty line are the panel's; everything below the |
| 1255 | line is share.js's. --> |
| 1256 | <div class="imp-view" id="social-share" hidden> |
| 1257 | <div class="rail-note imp-off" id="social-share-off" data-i18n="social.share_off">Sharing is not available in this build.</div> |
| 1258 | <div class="imp-list" id="social-share-list"></div> |
| 1259 | </div> |
| 1260 | |
| 1261 | <!-- Proposals, and the compose box that makes them. Note-capture merged |
| 1262 | in here: a note is written at the top and posted straight as a |
| 1263 | proposal, verbatim or polished by the model first. There is no |
| 1264 | separate Notes view and no Keep -- a note that cannot be sent yet |
| 1265 | waits in the queue below the box, not in a list of its own. --> |
| 1266 | <div class="imp-view" id="improve-props-view"> |
| 1267 | <div class="imp-write imp-compose"> |
| 1268 | <textarea class="imp-box" id="improve-box" rows="3" |
| 1269 | placeholder="Where it is, what you expected, and what happened instead." |
| 1270 | data-i18n-placeholder="social.box_ph" |
| 1271 | aria-label="Write a note about Daimond" data-i18n-aria-label="social.box_label"></textarea> |
| 1272 | <!-- What goes with the note, in the exact characters that will |
| 1273 | travel, in a row with a closer on it. Closing it takes the |
| 1274 | line off the wire as well as off the screen. --> |
| 1275 | <div class="imp-with" id="improve-with" hidden> |
| 1276 | <div class="imp-with-body"> |
| 1277 | <span class="imp-with-label" data-i18n="social.with">What goes with it</span> |
| 1278 | <div class="imp-with-text" id="improve-with-text"></div> |
| 1279 | </div> |
| 1280 | <button type="button" class="ui-close imp-with-off" data-act="improve-with-off" |
| 1281 | title="Close" data-i18n-title="common.close" |
| 1282 | aria-label="Take the details off this note" data-i18n-aria-label="social.with_off"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button> |
| 1283 | </div> |
| 1284 | <div class="imp-acts" id="improve-acts"> |
| 1285 | <button type="button" class="imp-send" data-act="improve-post" data-i18n="social.post" |
| 1286 | title="Post exactly what is above to the forge as a proposal." data-i18n-title="social.post_help">Post</button> |
| 1287 | <button type="button" class="imp-note-copy" data-act="improve-polish" data-i18n="social.polish" |
| 1288 | title="Let the model rewrite it as a proposal, then post that." data-i18n-title="social.polish_help">Polish & post</button> |
| 1289 | <span class="imp-as" id="improve-as"></span> |
| 1290 | </div> |
| 1291 | <div class="imp-say" id="improve-say" role="status" aria-live="polite"></div> |
| 1292 | <!-- Said after a note is raised (posted, or queued): "Raised — see it |
| 1293 | in Improve", where Improve is a press that opens the hub. Drawn by |
| 1294 | js/improve.js. --> |
| 1295 | <div class="imp-raised" id="improve-raised" role="status" aria-live="polite" hidden></div> |
| 1296 | </div> |
| 1297 | <!-- A proposal this device raised that was DECLINED comes back here with |
| 1298 | the operator's one-line reason. js/improve.js OWNS it, reads it on |
| 1299 | panel open under this device's own voice, and acks each note (per |
| 1300 | `when` key) once it has been shown and dismissed. Hidden while empty. --> |
| 1301 | <div class="imp-returned" id="improve-returned" role="status" aria-live="polite" hidden></div> |
| 1302 | <!-- Notes that could not be sent yet: written offline, or a send that |
| 1303 | failed. js/improve.js OWNS this and clears it on every draw; it is |
| 1304 | drained automatically when the browser comes back online. |
| 1305 | |
| 1306 | CAPTURE ONLY. The browse/vote list of all proposals is NOT here any |
| 1307 | more -- it is the Improve hub (js/tracker.js, #panel-tracker). This |
| 1308 | view writes a proposal and shows what is waiting to send; reading and |
| 1309 | settling live in the hub, so the two surfaces no longer duplicate a |
| 1310 | list. --> |
| 1311 | <div class="imp-queue" id="improve-queue"></div> |
| 1312 | </div> |
| 1313 | |
| 1314 | <!-- Settings: the voice this device posts with, and the drafts the model |
| 1315 | prepared. js/improve.js OWNS the inside and draws all of it. --> |
| 1316 | <div class="imp-view" id="social-settings" hidden> |
| 1317 | <div class="imp-settings" id="improve-settings"></div> |
| 1318 | </div> |
| 1319 | </aside> |
| 1320 | <!-- ── The Proposals tracker (js/tracker.js) ────────────────────────── |
| 1321 | A read-first view of Daimond's own development, tracked as proposals on |
| 1322 | the Oregami forge. Reads through the gateway (/api/improve); the owner's |
| 1323 | settle controls are dormant until an admin voice is minted. js/tracker.js |
| 1324 | OWNS #tracker-view and draws all of it; it self-mounts and reads the |
| 1325 | listing the first time the panel is shown. --> |
| 1326 | <aside class="panel tracker" id="panel-tracker" data-panel="tracker" data-zone="dock" data-label="Improve" data-i18n-label="panel.tracker"> |
| 1327 | <div class="railhead"><span role="heading" aria-level="2" data-i18n="panel.tracker">Improve</span> |
| 1328 | <button class="addbtn panel-close" data-close="tracker" title="Close panel" data-i18n-title="common.close_panel"> |
| 1329 | <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> |
| 1330 | </button> |
| 1331 | </div> |
| 1332 | <div id="tracker-view" class="trk"></div> |
| 1333 | </aside> |
| 1334 | </div> |
| 1335 | |
| 1336 | <!-- Mobile: the panel chip row, which is the DESKTOP header's row moved down |
| 1337 | here (js/mobile.js, `placeChips`) and scrolled sideways. It held four |
| 1338 | hard-wired destinations -- Chat, Email, Files, Agents -- until 2026-08-28, |
| 1339 | which named four of seventeen panels and left the other thirteen behind a |
| 1340 | drawer. The owner's verdict on that bar: "the footer in mobile view is |
| 1341 | currently useless to me". |
| 1342 | The row is MOVED, not copied: `#panel-tags` is the one row the layout |
| 1343 | engine renders, the one the unseen counts are painted on, and the one the |
| 1344 | gallery asks whether a panel is on. Two of it would be two to keep in |
| 1345 | step. The chat is still the floor; a "thing" (Web, Doc, Message, Compose, |
| 1346 | Tools) rises as a sheet over it, and the rail is still the left drawer |
| 1347 | behind the hamburger. --> |
| 1348 | <nav class="mnav" id="mnav" aria-label="Panels" data-i18n-aria-label="gallery.title"></nav> |
| 1349 | </div> |
| 1350 | |
| 1351 | <!-- Phone shell: the scrim behind the drawer, and the sheet that raises a |
| 1352 | "thing" over the chat. Both are inert on desktop (see mobile.css). --> |
| 1353 | <div id="scrim"></div> |
| 1354 | <div id="msheet" aria-label="Panel" data-i18n-aria-label="sheet.panel"> |
| 1355 | <div class="msheet-grab" id="msheet-grab"> |
| 1356 | <span class="msheet-bar" aria-hidden="true"></span> |
| 1357 | <span class="msheet-title" id="msheet-title"></span> |
| 1358 | <!-- Drawn, like every other closer in the app. The '✕' character it used to |
| 1359 | carry sat wherever the platform's fallback font put it, and the sheet |
| 1360 | is the one surface where the closer is the only way out that does not |
| 1361 | need a drag. --> |
| 1362 | <button class="msheet-close ui-close" id="msheet-close" title="Close" aria-label="Close the sheet" |
| 1363 | data-i18n-title="common.close" data-i18n-aria-label="sheet.close"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button> |
| 1364 | </div> |
| 1365 | <div class="msheet-scroll" id="msheet-body"></div> |
| 1366 | <div class="msheet-ask" id="msheet-ask"> |
| 1367 | <input id="msheet-ask-input" type="text" placeholder="Ask about this…" autocomplete="off" autocapitalize="sentences" spellcheck="true" data-i18n-placeholder="sheet.ask_ph"> |
| 1368 | <button id="msheet-ask-send" title="Ask" aria-label="Ask" data-i18n-title="sheet.ask" data-i18n-aria-label="sheet.ask">➤</button> |
| 1369 | </div> |
| 1370 | </div> |
| 1371 | |
| 1372 | <!-- Settings, where there is no rail to hold them. On a phone, and in the band |
| 1373 | where the rail folds away, there is no room for a form beside a live chat — |
| 1374 | so the settings view is MOVED into this card and back out again. There is |
| 1375 | one settings form in the document, not two. |
| 1376 | |
| 1377 | The card carries no heading of its own: the hosted view brings its head with |
| 1378 | it, so the title names what is actually on screen. A static "Settings" here |
| 1379 | contradicted every view but one. --> |
| 1380 | <div id="settings-modal" class="modal" style="display:none;"> |
| 1381 | <div class="modal-card"> |
| 1382 | <div id="settings-slot"></div> |
| 1383 | <button class="modal-close" id="settings-close" data-i18n="common.close">Close</button> |
| 1384 | </div> |
| 1385 | </div> |
| 1386 | |
| 1387 | <!-- Identity modal: create a passphrase on first run, unlock on return. The |
| 1388 | passphrase encrypts the stored API key and is the on-device Daimond identity. --> |
| 1389 | <div id="identity-modal" class="modal" style="display:none;"> |
| 1390 | <div class="modal-card"> |
| 1391 | <img class="login-logo" id="id-logo" src="assets/daimond_word.svg" alt="Daimond" data-light="assets/daimond_word_dark.svg" data-dark="assets/daimond_word.svg"> |
| 1392 | <!-- THE FRONT DOOR, for somebody who arrived from daimond.app and has never |
| 1393 | seen this app. |
| 1394 | |
| 1395 | "Launch app" on the splash page lands here, on a form headed "Create |
| 1396 | your account" — which is a true instruction and a poor answer, because |
| 1397 | the account it makes is refused by a gateway in closed beta and the |
| 1398 | person is told so only afterwards. The three ways in existed (the |
| 1399 | application form at /apply.html, its waitlist half, and the passcode |
| 1400 | dialog in js/passcode.js); nothing at the door pointed at any of them. |
| 1401 | |
| 1402 | ABOVE the title, and above the form, because it is the answer to |
| 1403 | "what is this and can I have it" and the form is the answer to a |
| 1404 | question a stranger has not asked yet. It is three names on one |
| 1405 | wrapped row and one line of lead — this card already runs 874px and |
| 1406 | scrolls on a laptop, so the strip is the smallest thing that can |
| 1407 | carry three routes. |
| 1408 | |
| 1409 | THE HEAD IS INSIDE THE LEAD, on one line, and that is a correction. |
| 1410 | Written as a head ABOVE the lead the strip stood 176px tall and put |
| 1411 | "Create account" 25px under the card's own bottom edge — a first |
| 1412 | visitor could not see the button they were brought here to press. |
| 1413 | The question and the answer are one sentence, so they are set as |
| 1414 | one, and the two keys stay two so the wording of either can change |
| 1415 | without the other. The rest of that 176px is answered by the sticky |
| 1416 | foot at `.id-foot` below, which is what actually guarantees the |
| 1417 | button; this only stops the strip from spending more of the card |
| 1418 | than it needs. |
| 1419 | |
| 1420 | SHOWN TO A STRANGER ONLY. `syncDoors` in js/daimond.js draws it only |
| 1421 | while creating, and only when no account in this browser has ever |
| 1422 | held an identity. Somebody unlocking sees nothing; somebody adding a |
| 1423 | second account to a browser that already has one sees nothing. --> |
| 1424 | <div class="id-doors" id="id-doors" style="display:none"> |
| 1425 | <p class="id-doors-lead"><span class="id-doors-head" data-i18n="door.head">New to Daimond?</span> <span data-i18n="door.lead">It is in closed testing, so accounts are given out by passcode.</span></p> |
| 1426 | <div class="id-doors-row"> |
| 1427 | <!-- Root-absolute, and a new tab, for the reasons the passcode card's |
| 1428 | own link states: the app is one document at the site root, and |
| 1429 | leaving the page would take a half-typed passphrase with it. --> |
| 1430 | <a class="id-door" id="id-door-test" href="/apply.html?for=test" target="_blank" rel="noopener" data-i18n="door.test">Apply to test</a> |
| 1431 | <a class="id-door" id="id-door-wait" href="/apply.html?for=waitlist" target="_blank" rel="noopener" data-i18n="door.waitlist">Join the waitlist</a> |
| 1432 | <!-- A DISCLOSURE, not a one-way switch. Pressing it reveals the note |
| 1433 | below (there is no device key to redeem a code onto yet, so the |
| 1434 | order has to be said rather than the field drawn); pressing it |
| 1435 | again puts the note away. It was neither before, and |
| 1436 | `verify_reversible` was right to call it the only control on this |
| 1437 | screen with no way back. `aria-expanded` is what says so to a |
| 1438 | reader, and `aria-controls` names the thing it opens. --> |
| 1439 | <button type="button" class="id-door" id="id-door-code" aria-expanded="false" aria-controls="id-doors-note" data-i18n="door.code">I have a passcode</button> |
| 1440 | </div> |
| 1441 | <p class="id-doors-note" id="id-doors-note" style="display:none"></p> |
| 1442 | </div> |
| 1443 | <h2 id="id-title">Protect this device</h2> |
| 1444 | <p class="cfg-lead" id="id-lead"></p> |
| 1445 | <!-- Several accounts on one browser: pick whose to unlock, or add another. Built in JS, and |
| 1446 | shown only when there is more than one account or one to add. --> |
| 1447 | <div class="id-accounts" id="id-accounts" style="display:none"></div> |
| 1448 | <!-- A REAL form with REAL password fields, so the browser and the OS |
| 1449 | keychain recognise a credential and offer to keep it. The passphrase |
| 1450 | is generated (see wordlist.js), which is what makes accepting that |
| 1451 | offer safe: there is nothing weak or reused to save. The name doubles |
| 1452 | as the username a manager files the entry under, so several accounts |
| 1453 | in one browser stay distinguishable. --> |
| 1454 | <form id="id-form" autocomplete="on"> |
| 1455 | <div class="id-fields"> |
| 1456 | <div id="id-name-row"> |
| 1457 | <input type="text" id="id-name" name="username" placeholder="Your name" data-i18n-placeholder="identity.your_name" autocomplete="username" autocapitalize="off" autocorrect="off" spellcheck="false" maxlength="40"> |
| 1458 | </div> |
| 1459 | <!-- The generated passphrase, shown so it can be written down. It |
| 1460 | mirrors the field below rather than holding a second copy, so a |
| 1461 | browser-suggested password cannot diverge from what is displayed. --> |
| 1462 | <div class="pass-gen" id="id-genbox" style="display:none"> |
| 1463 | <div class="pass-gen-words" id="id-genwords"></div> |
| 1464 | <div class="pass-gen-acts"> |
| 1465 | <button type="button" class="pass-gen-btn" id="id-regen" data-i18n="identity.generate_another">Generate another</button> |
| 1466 | <button type="button" class="pass-gen-btn" id="id-gencopy" data-i18n="common.copy">Copy</button> |
| 1467 | </div> |
| 1468 | <p class="pass-gen-note" id="id-gennote"></p> |
| 1469 | <label class="pass-gen-ack"><input type="checkbox" id="id-wrote"><span data-i18n="identity.wrote_it_down">I have written this down somewhere safe</span></label> |
| 1470 | </div> |
| 1471 | <div class="pass-wrap" id="id-pass-row"> |
| 1472 | <input type="password" id="id-pass" name="password" placeholder="Passphrase" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" data-i18n-placeholder="identity.passphrase"> |
| 1473 | <button type="button" class="pass-eye icon-btn" id="id-pass-eye" title="Show passphrase" aria-label="Show passphrase" data-i18n-title="identity.show_passphrase" data-i18n-aria-label="identity.show_passphrase"></button> |
| 1474 | </div> |
| 1475 | <div class="pass-wrap" id="id-pass2-row"> |
| 1476 | <input type="password" id="id-pass2" placeholder="Confirm passphrase" autocomplete="new-password" autocapitalize="off" autocorrect="off" spellcheck="false" data-i18n-placeholder="identity.confirm_passphrase"> |
| 1477 | <button type="button" class="pass-eye icon-btn" id="id-pass2-eye" title="Show passphrase" aria-label="Show passphrase" data-i18n-title="identity.show_passphrase" data-i18n-aria-label="identity.show_passphrase"></button> |
| 1478 | </div> |
| 1479 | </div> |
| 1480 | <div id="id-error" class="login-error"></div> |
| 1481 | <!-- THE ONE BUTTON THIS SCREEN EXISTS FOR, in a foot that stays on |
| 1482 | screen while the card scrolls (see `.id-foot` in css/app.css). |
| 1483 | A wrapper and not `position: sticky` on the button itself, |
| 1484 | because the button's own 12px top margin would be a transparent |
| 1485 | band for the form to scroll through; the foot carries that gap |
| 1486 | as padding and paints the card's ground behind it. --> |
| 1487 | <div class="id-foot"> |
| 1488 | <button type="submit" id="id-primary">Create passphrase</button> |
| 1489 | </div> |
| 1490 | </form> |
| 1491 | <button type="button" id="id-choose" class="id-choose" style="display:none" data-i18n="identity.choose_own">Choose my own passphrase instead</button> |
| 1492 | <button type="button" id="id-skip" class="modal-close" data-i18n="identity.skip">Skip for now</button> |
| 1493 | </div> |
| 1494 | </div> |
| 1495 | |
| 1496 | <!-- accounts.js FIRST: it installs the per-account storage namespace before any |
| 1497 | module reads or writes localStorage. --> |
| 1498 | <script src="js/accounts.js"></script> |
| 1499 | <!-- breadcrumb.js immediately after: it records the boot itself, and an error |
| 1500 | thrown by anything below has to land in the trail rather than in a console |
| 1501 | nobody on a phone can open. --> |
| 1502 | <script src="js/breadcrumb.js"></script> |
| 1503 | <!-- safe.js next, and it must be next: it reads the trail breadcrumb.js has |
| 1504 | just written to decide whether this app is looping, and sync.js consults |
| 1505 | its answer from the first moment a session exists. --> |
| 1506 | <script src="js/safe.js"></script> |
| 1507 | <!-- i18n.js next: it owns the language and the display currency, and every |
| 1508 | money formatter in the app funnels through it. The English table follows |
| 1509 | immediately, because it is both the baseline and the fallback. --> |
| 1510 | <script src="js/i18n.js"></script> |
| 1511 | <script src="i18n/en.js"></script> |
| 1512 | <!-- closer.js right after the words it uses: it builds the one cross every |
| 1513 | dialog, popover, drawer and sheet wears, and it is loaded before ANY of |
| 1514 | them so a classic script and the wasm module can both reach for it. --> |
| 1515 | <script src="js/closer.js"></script> |
| 1516 | <!-- journal.js: the write-ahead log for in-flight work. Loaded early so it is |
| 1517 | ready before the first turn. --> |
| 1518 | <script src="js/journal.js"></script> |
| 1519 | <script src="js/marked.min.js"></script> |
| 1520 | <script src="js/render.js"></script> |
| 1521 | <!-- crystal.js beside render.js: it is the other renderer. It owns the sandboxed |
| 1522 | frame a Diamond's page runs in, the `dc` channel that page speaks, the |
| 1523 | shipped default page and the built-in view that replaces a page which fails. |
| 1524 | Classic, before the module, so window.DaimondCrystal exists when the app |
| 1525 | mounts one. --> |
| 1526 | <script src="js/crystal.js"></script> |
| 1527 | <!-- viewer.js: the Doc panel's file viewer, so that opening a PDF or a PNG shows |
| 1528 | the document rather than a screenful of replacement characters. Classic, |
| 1529 | before the module, so window.DaimondViewer exists when the app reaches for |
| 1530 | it -- the same rule every other panel module here loads under. --> |
| 1531 | <script src="js/viewer.js"></script> |
| 1532 | <script src="js/pricing.js"></script> |
| 1533 | <script src="js/ledger.js"></script> |
| 1534 | <!-- money.js: which pot of money a figure belongs to, and what may honestly be |
| 1535 | said about it. Pure wording rules, before the app that draws them. --> |
| 1536 | <script src="js/money.js"></script> |
| 1537 | <!-- signals.js: the local usage index the Optimiser reads. After ledger.js, |
| 1538 | which it does not depend on but sits beside conceptually: the ledger is the |
| 1539 | account's money, this is how the account is worked. --> |
| 1540 | <script src="js/signals.js"></script> |
| 1541 | <script src="js/governor.js"></script> |
| 1542 | <script src="js/pause.js"></script> |
| 1543 | <!-- trash.js BEFORE sync.js, daimond.js and graph.js: all three ask it whether |
| 1544 | something has been deleted while they build the lists they draw, so the |
| 1545 | store has to be there when they do. --> |
| 1546 | <!-- improve.js next: the panel a note about Daimond is written in, and the |
| 1547 | proposals made from notes are read and voted on in. Nothing leaves this |
| 1548 | device until somebody presses Send on one note. See dev/IMPROVE_CONTRACT.md. --> |
| 1549 | <script src="js/trash.js"></script> |
| 1550 | <!-- rekey.js BEFORE every module that seals a secret under the passphrase — |
| 1551 | voice.js is the first of them. Each registers with it AT LOAD, beside its |
| 1552 | own sealing code, so that a passphrase change carries every secret across |
| 1553 | without anybody keeping a list. See the header of js/rekey.js. --> |
| 1554 | <script src="js/rekey.js"></script> |
| 1555 | <!-- voice.js BEFORE improve.js: the panel presents a voice to write with, and |
| 1556 | this holds it. Before identity.js is fine — it reaches DaimondIdentity only |
| 1557 | inside a call, never while loading. --> |
| 1558 | <script src="js/voice.js"></script> |
| 1559 | <!-- drafts.js BEFORE every module that owns a text box — improve.js is the |
| 1560 | first of them, daimond.js's composer is the largest. It holds what somebody |
| 1561 | is half-way through typing so a reload does not take it, and it is loaded |
| 1562 | early because a box binds to it as it is drawn. It sends nothing and |
| 1563 | reaches no network: see the header, which says why that is not the send |
| 1564 | queue dev/IMPROVE_CONTRACT.md §4 forbids. --> |
| 1565 | <script src="js/drafts.js"></script> |
| 1566 | <script src="js/improve.js"></script> |
| 1567 | <script src="js/triage.js"></script> |
| 1568 | <script src="js/tracker.js"></script><!-- the Proposals tracker: read-first, self-mounts into #tracker-view --> |
| 1569 | <!-- approvelist.js AFTER triage.js: it reads triage's held plan into a local |
| 1570 | review queue and batch-sends the ticked drafts through improve.js's forge |
| 1571 | door. It makes its own host element, so it needs no markup here. --> |
| 1572 | <script src="js/approvelist.js"></script> |
| 1573 | <script src="js/triggers.js"></script> |
| 1574 | <script src="js/verify.js"></script> |
| 1575 | <!-- wordlist.js before identity.js: the create screen generates a passphrase |
| 1576 | before any identity exists to hold it. --> |
| 1577 | <script src="js/wordlist.js"></script> |
| 1578 | <!-- The pure-JS Ed25519/X25519 fallback, before identity.js so it is present |
| 1579 | when an unlock on an engine without WebCrypto for those curves needs it. |
| 1580 | Vendored noble-curves loads first; curvefallback.js wraps it. --> |
| 1581 | <script src="js/vendor/noble-curves.min.js"></script> |
| 1582 | <script src="js/curvefallback.js"></script> |
| 1583 | <script src="js/identity.js"></script> |
| 1584 | <!-- passkey.js AFTER identity.js: it builds on DaimondIdentity to seal and |
| 1585 | recover the passphrase behind a WebAuthn PRF credential. --> |
| 1586 | <script src="js/passkey.js"></script> |
| 1587 | <script src="js/gateway.js"></script> |
| 1588 | <!-- telemetry.js: what a beta tester who agreed sends, and the whole of it — |
| 1589 | twenty events, three integers each, and no text field anywhere in the |
| 1590 | payload. Read the top of the file; it is written to be read. |
| 1591 | |
| 1592 | LOADED HERE, BEFORE passcode.js, which is the only thing that can start it: |
| 1593 | the consent card shown when a passcode is redeemed, and the same question in |
| 1594 | the Credits drawer, where it is also turned off again. Loading it does not |
| 1595 | arm it — there is no flag to forget to check, only a recorder that does not |
| 1596 | exist until somebody presses a button — and dev/verify_telemetry.mjs proves |
| 1597 | that at the network, in a session that does real work and sends nothing. |
| 1598 | |
| 1599 | It stayed OUT of this list until three things were true, and each is now a |
| 1600 | check in that verifier: consent can be taken back and taking it back stops a |
| 1601 | batch already queued; something actually emits, so the question is not |
| 1602 | asking permission to send nothing; and the Privacy Policy describes what is |
| 1603 | sent, at #beta-telemetry, which the consent card links to. --> |
| 1604 | <script src="js/telemetry.js"></script> |
| 1605 | <!-- passcode.js AFTER gateway.js: the refusal it draws is raised by |
| 1606 | gateway.js, and the redemption behind its one button is |
| 1607 | DaimondGateway.redeemPasscode. Classic, before the module, so |
| 1608 | window.DaimondCredits exists when daimond.js first draws the Credits |
| 1609 | view. --> |
| 1610 | <script src="js/passcode.js"></script> |
| 1611 | <script src="js/chunks.js"></script> |
| 1612 | <script src="js/cloud.js"></script> |
| 1613 | <script src="js/sync.js"></script> |
| 1614 | <script src="js/pairing.js"></script> |
| 1615 | <!-- trust.js AFTER pairing.js, whose dialog frame and QR drawer it borrows, and |
| 1616 | after identity.js (the keys), improve.js (the Social panel it renders the |
| 1617 | People list into) and gateway.js. It loads js/qrscan.js itself, on demand, |
| 1618 | when somebody opens the scanner — a camera decoder is not something to |
| 1619 | fetch on every boot for the readers who never scan anything. --> |
| 1620 | <script src="js/trust.js"></script> |
| 1621 | <!-- post.js AFTER trust.js, which holds the keys it seals to and the People |
| 1622 | list it names, and after sync.js, whose parcel carries its store. --> |
| 1623 | <script src="js/post.js"></script> |
| 1624 | <!-- group.js AFTER post.js, which owns the store its rosters live in, the seal |
| 1625 | they travel under and the region it draws into. It holds no storage of its |
| 1626 | own and no seal of its own. --> |
| 1627 | <script src="js/group.js"></script> |
| 1628 | <!-- report.js and share.js AFTER post.js, which holds the messages the one |
| 1629 | reports on and the seal the other borrows. Both couple to post.js through |
| 1630 | one delegated listener each and touch none of its DOM, so the order is |
| 1631 | about what is on the page when they attach and nothing more. --> |
| 1632 | <script src="js/report.js"></script> |
| 1633 | <script src="js/share.js"></script> |
| 1634 | <script src="js/mail.js"></script> |
| 1635 | <script src="js/tools.js"></script> |
| 1636 | <script src="js/models.js"></script> |
| 1637 | <!-- After models.js: window.DaimondOcr does the authenticated OpenRouter / Mistral POSTs that |
| 1638 | turn a PDF or an image into text for `file_read` and the `ocr` tool, and it reads the |
| 1639 | provider key from DaimondModels, so that must exist first. --> |
| 1640 | <script src="js/ocr.js"></script> |
| 1641 | <!-- After models.js, which it is a miniature of: same registry shape, same |
| 1642 | sealing, same credits-or-your-own-key question asked about search. --> |
| 1643 | <script src="js/search.js"></script> |
| 1644 | <script src="js/autoreload.js"></script> |
| 1645 | <script src="js/web.js"></script> |
| 1646 | <script src="js/hand.js"></script> |
| 1647 | <script src="js/handpty.js"></script> |
| 1648 | <script src="js/terminal.js"></script> |
| 1649 | <script src="js/spend.js"></script> |
| 1650 | <!-- pwa.js beside updater.js, deliberately: the updater reads build.json and pwa.js |
| 1651 | passes what it read to the shell worker, so the worker's cache is emptied by |
| 1652 | the same reading that moves the update chip. --> |
| 1653 | <script src="js/pwa.js"></script> |
| 1654 | <script src="js/updater.js"></script> |
| 1655 | <script src="js/workspace.js"></script> |
| 1656 | <script src="js/release.js"></script> |
| 1657 | <script src="js/graph.js"></script> |
| 1658 | <!-- The persistent desktop peer: window.DaimondPeer / DaimondLease / |
| 1659 | DaimondPresence, consumed by the guarded glue in daimond.js. Money-safe |
| 1660 | (the sequential double-bill is closed, proven in live two-context QA). See |
| 1661 | dev/PEER_DESIGN.md. --> |
| 1662 | <script src="js/peer.js"></script> |
| 1663 | |
| 1664 | <!-- The phone shell (drawer + sheet). Classic, before the module, so |
| 1665 | window.DaimondSheet/DaimondShell exist when the app wires into them. --> |
| 1666 | <script src="js/mobile.js"></script> |
| 1667 | <!-- fullscreen.js AFTER mobile.js: it reads DaimondShell.isPhone for the phone gate. --> |
| 1668 | <script src="js/fullscreen.js"></script> |
| 1669 | <!-- The permission ladder's surface. Classic, before the module, so |
| 1670 | window.DaimondHandMode exists when daimond.js hands it the wasm setter. --> |
| 1671 | <script src="js/handmode.js"></script> |
| 1672 | <!-- The Terms and the Privacy Policy, in Daimond's own Web panel rather than |
| 1673 | on another website, and the notices two of their clauses promise before |
| 1674 | something of the user's lapses. legal.js first: lapse.js links into a |
| 1675 | clause through it. --> |
| 1676 | <script src="js/legal.js"></script> |
| 1677 | <script src="js/lapse.js"></script> |
| 1678 | <!-- Interaction recording, for turning a path somebody actually took into a |
| 1679 | permanent check (dev/replay.mjs). INERT until a person presses Ctrl+Alt+R: |
| 1680 | it binds one keydown listener at load and nothing else. See dev/RECORD.md |
| 1681 | for what it captures, what it refuses to look at, and why. --> |
| 1682 | <script src="js/record.js"></script> |
| 1683 | <script type="module" src="js/daimond.js"></script> |
| 1684 | </body> |
| 1685 | </html> |