<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="design_doc_mode" content="canvas">
<title>Settings page redesign</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
body { margin: 0; background: #e9e8e4; color: #1a1b1f; font-family: "Geist", -apple-system, system-ui, sans-serif; }
a { color: #4a50b5; }
a:hover { color: #1a1b1f; }
.dv-intro { max-width: 980px; padding: 40px 44px 8px; font-size: 13.5px; line-height: 1.55; color: #3a3c42; }
.dv-intro h1 { margin: 0 0 10px; font-size: 22px; font-weight: 600; color: #1a1b1f; }
.dv-intro ul { margin: 6px 0 0; padding-left: 18px; }
.dv-turn { padding: 32px 44px 36px; border-bottom: 1px solid rgba(0, 0, 0, 0.08); }
.dv-thd { display: flex; align-items: baseline; gap: 10px; margin: 0 0 20px; }
.dv-tid { padding: 3px 7px; border-radius: 4px; background: #1a1a1a; color: #fff; font: 600 10px "Geist Mono", ui-monospace, monospace; text-decoration: none; }
.dv-tname { font: 600 14px/1.2 "Geist", system-ui, sans-serif; }
.dv-opts { display: flex; flex-wrap: wrap; gap: 32px; align-items: flex-start; }
.dv-opt { display: flex; flex: none; flex-direction: column; gap: 9px; }
.dv-olabel { display: flex; align-items: baseline; gap: 8px; max-width: 1440px; font-size: 12px; color: rgba(0, 0, 0, 0.6); }
.dv-oid { padding: 3px 7px; border-radius: 5px; background: rgba(0, 0, 0, 0.08); color: #1a1a1a; font: 600 10.5px "Geist Mono", ui-monospace, monospace; text-decoration: none; }
.dv-pick { padding: 2px 7px; border-radius: 5px; background: #5e64c9; color: #fff; font-size: 10.5px; font-weight: 600; }
.dv-card { overflow: hidden; border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); }
.dv-next { margin: 22px 0 0; font-size: 12px; color: rgba(0, 0, 0, 0.55); }
.dv-notes { max-width: 1440px; margin: 18px 0 0; font-size: 12.5px; line-height: 1.55; color: #3a3c42; }
.dv-notes li { margin: 2px 0; }
/* themes: the viewer's tokens.css and light.css */
.th-dark { --side: #0d0e10; --bg: #131416; --raised: #18191c; --hover: #1d1e22; --sel: #212329; --border: #25272b; --border-2: #2f3136; --border-3: #3a3d44; --line: #22252b; --bg-2: #101216; --text: #e6e7ea; --text-2: #a9acb3; --text-3: #83868e; --text-4: #6b6e76; --accent: #5e64c9; --accent-text: #a3a8f0; --accent-dim: #2a2d5a; --danger: #df8b7e; --sw-off: #3a3d44; --knob: #cfd2d8; --shadow: 0 10px 32px rgba(0, 0, 0, 0.5); --scrim: rgba(0, 0, 0, 0.5); color-scheme: dark; }
.th-light { --side: #f5f5f7; --bg: #ffffff; --raised: #f6f6f8; --hover: #efeff2; --sel: #e8e9ee; --border: #e5e6ea; --border-2: #d9dbe0; --border-3: #c9ccd3; --line: #e8e9ec; --bg-2: #fafafb; --text: #1a1b1f; --text-2: #474a52; --text-3: #6b6e77; --text-4: #8b8e97; --accent: #5e64c9; --accent-text: #4a50b5; --accent-dim: #e5e6fa; --danger: #bf4f3d; --sw-off: #cfd1d7; --knob: #ffffff; --shadow: 0 10px 32px rgba(20, 20, 40, 0.16); --scrim: rgba(20, 20, 30, 0.28); color-scheme: light; }
.scr { position: relative; overflow: hidden; background: var(--bg); color: var(--text); font-size: 13px; line-height: 1.45; -webkit-font-smoothing: antialiased; }
.scr *, .scr *::before, .scr *::after { box-sizing: border-box; }
.scr button { font: inherit; color: inherit; }
/* viewer shell */
.shell { display: grid; grid-template-columns: 236px minmax(0, 1fr); height: 100%; }
.side { display: flex; flex-direction: column; gap: 2px; padding: 0 10px; border-right: 1px solid var(--border); background: var(--side); }
.side-head { display: flex; align-items: center; gap: 10px; height: 48px; padding: 0 8px; font-weight: 600; font-size: 14px; }
.badge { display: grid; place-items: center; width: 20px; height: 20px; border-radius: 5px; background: #c4508a; color: #fff; font-size: 11px; }
.side-sec { margin: 14px 0 4px; padding: 0 8px; color: var(--text-3); font-size: 11.5px; }
.side-item { display: flex; align-items: center; gap: 10px; height: 30px; padding: 0 8px; border-radius: 6px; color: var(--text-2); }
.side-item.on { background: var(--sel); color: var(--text); }
.side-ico { width: 12px; height: 12px; border: 1.4px solid currentColor; border-radius: 3px; opacity: 0.7; }
.side-foot { margin-top: auto; padding: 12px 0; color: var(--text-4); font-size: 11px; }
.main { display: flex; flex-direction: column; min-width: 0; }
.top { display: flex; align-items: center; gap: 8px; height: 48px; padding: 0 16px; border-bottom: 1px solid var(--border); color: var(--text-3); }
.top b { color: var(--text); font-weight: 500; }
/* page bar */
.pbar { display: flex; align-items: center; gap: 14px; height: 52px; padding: 0 20px; border-bottom: 1px solid var(--border); }
.find { display: flex; align-items: center; gap: 8px; width: 300px; height: 30px; padding: 0 8px 0 10px; border: 1px solid var(--border-2); border-radius: 7px; background: var(--bg); color: var(--text-4); font-size: 12.5px; }
.find.active { border-color: var(--accent); color: var(--text); }
.find .q { flex: 1; }
.lens { position: relative; width: 11px; height: 11px; flex: none; border: 1.5px solid var(--text-3); border-radius: 50%; }
.lens::after { content: ""; position: absolute; right: -4px; bottom: -3px; width: 5px; height: 1.5px; background: var(--text-3); transform: rotate(45deg); }
.kbd { padding: 0 5px; border: 1px solid var(--border-2); border-radius: 4px; color: var(--text-4); font: 11px "Geist Mono", monospace; }
.clear { color: var(--text-3); font-size: 12px; }
.seg { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--border-2); border-radius: 7px; background: var(--bg-2); }
.seg span { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 5px; color: var(--text-3); font-size: 12px; white-space: nowrap; }
.seg span.on { background: var(--sel); color: var(--text); }
.seg em { color: var(--text-4); font-style: normal; font-variant-numeric: tabular-nums; }
.scope { margin-left: auto; color: var(--text-3); font-size: 12px; }
.env { display: inline-flex; align-items: center; gap: 6px; margin-left: 4px; padding: 1px 8px; border: 1px solid var(--border-2); border-radius: 10px; color: var(--text-2); }
.env::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
/* body */
.sbody { display: grid; grid-template-columns: 216px minmax(0, 1fr); flex: 1; min-height: 0; }
.nav { padding: 14px 10px; border-right: 1px solid var(--border); overflow: hidden; }
.nav-sec { margin: 12px 0 4px; padding: 0 10px; color: var(--text-4); font-size: 11px; letter-spacing: 0.02em; }
.nav-sec:first-child { margin-top: 0; }
.ni { display: flex; align-items: center; gap: 8px; height: 28px; padding: 0 10px; border-radius: 6px; color: var(--text-2); }
.ni.on { background: var(--sel); color: var(--text); }
.ni.gone { color: var(--text-4); }
.ni .nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ni .off { color: var(--text-4); font-size: 11px; }
.ni .n { color: var(--text-3); font-size: 11px; font-variant-numeric: tabular-nums; }
.ni .out { color: var(--text-4); font-size: 12px; }
.cdot { width: 6px; height: 6px; flex: none; border-radius: 50%; background: var(--accent); }
.content { overflow: hidden; padding: 26px 40px 40px; }
.col { display: flex; flex-direction: column; gap: 30px; max-width: 760px; }
.found { color: var(--text-3); font-size: 12.5px; }
/* group */
.gh { display: flex; align-items: flex-end; gap: 16px; margin-bottom: 10px; }
.gh-t { flex: 1; min-width: 0; }
.gh h2 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: -0.005em; }
.gh p { margin: 2px 0 0; color: var(--text-3); font-size: 12.5px; }
.how { display: inline-flex; align-items: center; gap: 4px; margin-left: 6px; padding: 0; border: 0; background: none; color: var(--accent-text) !important; font-size: 12px !important; }
.gh.v2 { align-items: center; padding-right: 15px; }
.gsw { display: inline-flex; align-items: center; gap: 10px; color: var(--text-3); font-size: 12px; }
.how.sm { margin-left: 2px; color: var(--text-3) !important; font-size: 11.5px !important; font-weight: 400; }
.seg .cdot { width: 5px; height: 5px; }
.gh-c { display: flex; align-items: center; gap: 8px; }
.card { border: 1px solid var(--border); border-radius: 10px; background: var(--raised); }
.card.off .row:not(.head) { opacity: 0.42; }
.offnote { padding: 0 14px 10px; color: var(--text-3); font-size: 12px; }
/* rows */
.row { position: relative; display: flex; align-items: center; gap: 16px; min-height: 44px; padding: 9px 14px; border-top: 1px solid var(--line); }
.card > .row:first-child { border-top: 0; }
.row.child { padding-left: 34px; }
.row.ind .l1 { color: var(--text-2); }
.row.ind { padding-left: 34px; }
.row.child.ind { padding-left: 54px; }
.row.head .l1 { font-weight: 500; }
.row.dimmed > .lab, .row.dimmed > .ctl { opacity: 0.42; }
.lab { flex: 1; min-width: 0; }
.l1 { display: flex; align-items: center; gap: 7px; color: var(--text); font-size: 13px; }
.hint { margin-top: 1px; color: var(--text-3); font-size: 12px; }
.ctl { position: relative; display: flex; flex: none; align-items: center; gap: 10px; }
mark { border-radius: 3px; background: var(--accent-dim); color: inherit; padding: 0 1px; }
.info { display: inline-grid; place-items: center; width: 15px; height: 15px; padding: 0; border: 1px solid var(--border-3); border-radius: 50%; background: none; color: var(--text-3) !important; font-size: 9.5px !important; font-weight: 600; }
.reset { padding: 0; border: 0; background: none; color: var(--accent-text) !important; font-size: 12px !important; }
/* controls */
.sw { position: relative; width: 28px; height: 16px; flex: none; border-radius: 8px; background: var(--sw-off); }
.sw i { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--knob); }
.sw.on { background: var(--accent); }
.sw.on i { left: 14px; background: #fff; }
.fsw { display: inline-flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px 0 6px; border: 1px solid var(--border-2); border-radius: 13px; background: var(--raised); color: var(--text-2); font-size: 12px; }
.pill { display: inline-flex; align-items: center; height: 22px; padding: 0 9px; border: 1px solid var(--border-2); border-radius: 11px; color: var(--text-3); font-size: 11.5px; white-space: nowrap; }
.cad { display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 8px 0 9px; border: 1px solid var(--border-2); border-radius: 6px; background: var(--bg-2); color: var(--text-2) !important; font-size: 12px !important; white-space: nowrap; }
.cad.open { border-color: var(--accent); color: var(--text) !important; }
.caret { width: 5px; height: 5px; margin-top: -3px; border-right: 1.4px solid var(--text-3); border-bottom: 1.4px solid var(--text-3); transform: rotate(45deg); }
.num { display: inline-flex; align-items: center; gap: 8px; color: var(--text-3); font-size: 12px; }
.field { height: 26px; padding: 0 8px; border: 1px solid var(--border-2); border-radius: 6px; background: var(--bg-2); color: var(--text); font-size: 12.5px; line-height: 24px; white-space: nowrap; overflow: hidden; }
.field.n { width: 62px; text-align: right; font-variant-numeric: tabular-nums; }
.field.t { width: 180px; }
.field.w { width: 250px; }
.field .ph { color: var(--text-4); }
.btn { display: inline-flex; align-items: center; height: 26px; padding: 0 10px; border: 1px solid var(--border-2); border-radius: 7px; background: var(--bg); color: var(--text-2) !important; font-size: 12px !important; white-space: nowrap; }
.btn.danger { border-color: color-mix(in oklab, var(--danger) 55%, var(--border-2)); color: var(--danger) !important; }
.btn.primary { border-color: var(--accent); background: var(--accent); color: #fff !important; }
.swatch { width: 20px; height: 20px; border: 1px solid var(--border-3); border-radius: 50%; background: #c4508a; }
.always { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; padding: 10px 14px; border-top: 1px solid var(--line); color: var(--text-3); font-size: 12px; }
.always b { color: var(--text-2); font-weight: 500; }
.danger-card { margin-top: 12px; }
/* cadence popover */
.pop { position: absolute; top: 32px; right: 38px; z-index: 5; display: flex; flex-direction: column; gap: 10px; width: 340px; padding: 12px; border: 1px solid var(--border-2); border-radius: 10px; background: var(--bg); box-shadow: var(--shadow); }
.pop-t { color: var(--text-3); font-size: 11.5px; }
.pop-r { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.pop .seg { flex-wrap: wrap; }
.pop-f { display: flex; justify-content: space-between; color: var(--text-3); font-size: 11.5px; }
/* phone */
.ph-scr { display: flex; flex-direction: column; height: 100%; }
.ph-status { display: flex; justify-content: space-between; height: 44px; padding: 14px 26px 0; font-size: 13px; font-weight: 600; }
.ph-bar { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 16px; }
.ph-title { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
.ph-back { display: inline-flex; align-items: center; gap: 6px; color: var(--accent-text); font-size: 15px; }
.chev-l, .chev-r { width: 8px; height: 8px; border-left: 1.8px solid currentColor; border-bottom: 1.8px solid currentColor; transform: rotate(45deg); }
.chev-r { transform: rotate(-135deg); color: var(--text-4); }
.ph-pad { display: flex; flex-direction: column; gap: 10px; padding: 4px 16px 10px; }
.ph-pad .find { width: auto; height: 40px; font-size: 15px; }
.ph-pad .seg { display: flex; }
.ph-pad .seg span { flex: 1; justify-content: center; min-height: 34px; font-size: 13.5px; }
.ph-list { flex: 1; overflow: hidden; padding: 0 16px; }
.ph-sec { margin: 16px 0 6px 4px; color: var(--text-3); font-size: 12.5px; }
.ph-card { border: 1px solid var(--border); border-radius: 12px; background: var(--raised); }
.ph-item { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 14px; border-top: 1px solid var(--line); font-size: 15px; }
.ph-item:first-child { border-top: 0; }
.ph-item .nm { flex: 1; }
.ph-item .off { color: var(--text-4); font-size: 13px; }
.ph-g .row { min-height: 52px; padding: 10px 14px; align-items: center; }
.ph-g .l1 { font-size: 14.5px; }
.ph-g .hint { font-size: 13px; }
.ph-g .row.ind { padding-left: 30px; }
.ph-g .sw { width: 40px; height: 24px; border-radius: 12px; }
.ph-g .sw i { width: 20px; height: 20px; }
.ph-g .sw.on i { left: 18px; }
.ph-g .cad { height: 32px; margin-top: 6px; font-size: 13px !important; }
.ph-g .field.n { height: 34px; width: 64px; line-height: 32px; font-size: 14px; }
.ph-gh { padding: 0 16px 12px; color: var(--text-3); font-size: 13.5px; }
.ph-bar .fsw { height: 34px; font-size: 13.5px; }
.sheet-scrim { position: absolute; inset: 0; background: var(--scrim); }
.sheet { position: absolute; right: 0; bottom: 0; left: 0; display: flex; flex-direction: column; gap: 14px; padding: 10px 18px 34px; border-top: 1px solid var(--border-2); border-radius: 16px 16px 0 0; background: var(--bg); }
.grab { align-self: center; width: 36px; height: 4px; border-radius: 2px; background: var(--border-3); }
.sheet h3 { margin: 0; font-size: 16px; font-weight: 600; }
.sheet .seg { display: grid; grid-template-columns: 1fr 1fr 1fr; }
.sheet .seg span { justify-content: center; min-height: 40px; font-size: 13.5px; }
.sheet .field.n { width: 96px; height: 44px; line-height: 42px; font-size: 17px; }
.sheet .btn { justify-content: center; height: 46px; font-size: 15px !important; }
</style>
</head>
<body>
<div class="dv-intro" data-screen-label="Brief">
    <h1>Settings, one page, grouped by what it changes</h1>
    <p>
        Every feature's settings sit together in one group, under a plain name; settings without a feature live in Journal, Viewer,
        Chat or Developer. The left list is the table of contents for one scrolling page, and search filters the same page. The
        lines the journal can say to the agent are gone from Settings. The viewer's own colors, type and controls throughout:
        switches, segmented buttons and small number fields, no select boxes.
    </p>
</div>
<section class="dv-turn" id="t2" data-screen-label="Turn 2">
    <div class="dv-thd"><a class="dv-tid" href="#t2">2</a><span class="dv-tname">After the critique: <a class="dv-oid" href="#1a">1a</a>, revised</span></div>
    <div class="dv-opts">
        <div class="dv-opt" id="2a">
            <div class="dv-olabel"><a class="dv-oid" href="#2a">2a</a><span class="dv-pick">Final</span>Dark, top of the page. The group switch now lines up with the row switches; Journal and Viewer say what they apply to.</div>
            <div class="dv-card" data-frame='{"v":2,"theme":"dark","start":"journal","hover":"identity.color"}'></div>
        </div>
        <div class="dv-opt" id="2a-light">
            <div class="dv-olabel"><a class="dv-oid" href="#2a">2a</a>Light, Messages, with the timing editor open. "Hold writes" now reads "Pause writes".</div>
            <div class="dv-card" data-frame='{"v":2,"theme":"light","start":"messages","pop":"messages.unread"}'></div>
        </div>
        <div class="dv-opt" id="2a-search">
            <div class="dv-olabel"><a class="dv-oid" href="#2a">2a</a>Search for "pause"</div>
            <div class="dv-card" data-frame='{"v":2,"theme":"dark","query":"pause"}'></div>
        </div>
        <div class="dv-opt" id="2a-off">
            <div class="dv-olabel"><a class="dv-oid" href="#2a">2a</a>Light, lower down: an always-on block, and a block that is off. Every block has a plain "How it works" link.</div>
            <div class="dv-card" data-frame='{"v":2,"theme":"light","start":"archive"}'></div>
        </div>
        <div class="dv-opt" id="2a-phone">
            <div class="dv-olabel"><a class="dv-oid" href="#2a">2a</a>Phone: groups</div>
            <div class="dv-card" data-frame='{"v":2,"kind":"phoneList","theme":"dark"}'></div>
        </div>
        <div class="dv-opt" id="2a-phone-group">
            <div class="dv-olabel"><a class="dv-oid" href="#2a">2a</a>Phone: one group</div>
            <div class="dv-card" data-frame='{"v":2,"kind":"phoneGroup","theme":"light","group":"messages"}'></div>
        </div>
        <div class="dv-opt" id="2a-phone-sheet">
            <div class="dv-olabel"><a class="dv-oid" href="#2a">2a</a>Phone: changing how often</div>
            <div class="dv-card" data-frame='{"v":2,"kind":"phoneGroup","theme":"dark","group":"messages","sheet":true}'></div>
        </div>
    </div>
    <ul class="dv-notes">
        <li><b>Taken:</b> the group switch is now a plain switch with On or Off beside it. It sits in the same column as every row switch, so the eye runs down one edge. The framed pill is gone.</li>
        <li><b>Taken:</b> "Hold writes" and "Hold tool calls" now say "Pause". The writes wait; they are not lost.</li>
        <li><b>Taken:</b> one help pattern everywhere. Groups and blocks both get a plain "How it works" link; the "?" circles are gone.</li>
        <li><b>Taken:</b> the Changed filter carries the same dot that marks a changed row and a group in the left list, so the dot explains itself.</li>
        <li><b>Taken:</b> Journal says "For the whole project, every environment" and Viewer says "For this browser only". The bar says the rest applies to environment main.</li>
        <li><b>Taken:</b> plainer group lines for Messages, Plans, Work tracking and Boards. The timing editor's second row reads "Or once, when".</li>
        <li><b>Not taken:</b> moving each feature's switch into the card as its first row. The heading already names the feature, and a row called "Messages" under the heading "Messages" says it twice.</li>
        <li><b>Not taken:</b> dropping "How it works". That text is the feature's only help in the viewer, so it stays behind one closed link.</li>
        <li><b>Not taken:</b> putting the unit inside the number field. A unit beside the field reads as a sentence ("5 times") and matches today's panel.</li>
    </ul>
    <p class="dv-next">Try next: "show the Off filter" · "show how it works opened on Messages" · "tighten the rows to 40px"</p>
</section>

<section class="dv-turn" id="t1" data-screen-label="Turn 1">
    <div class="dv-thd"><a class="dv-tid" href="#t1">1</a><span class="dv-tname">Settings page, first round</span></div>
    <div class="dv-opts">
        <div class="dv-opt" id="1a">
            <div class="dv-olabel"><a class="dv-oid" href="#1a">1a</a><span class="dv-pick">Picked</span>One scrolling page, the left list as its contents. Dark, top of the page; the project color row is hovered and shows Reset.</div>
            <div class="dv-card" data-frame='{"theme":"dark","start":"journal","hover":"identity.color"}'></div>
        </div>
        <div class="dv-opt" id="1a-light">
            <div class="dv-olabel"><a class="dv-oid" href="#1a">1a</a>Light, scrolled to Messages. A changed setting carries a dot; its timing opens a small editor in place.</div>
            <div class="dv-card" data-frame='{"theme":"light","start":"messages","pop":"messages.unread"}'></div>
        </div>
        <div class="dv-opt" id="1a-search">
            <div class="dv-olabel"><a class="dv-oid" href="#1a">1a</a>Search. The page keeps only matching rows, still in their groups; the left list shows how many match in each.</div>
            <div class="dv-card" data-frame='{"theme":"dark","query":"remind"}'></div>
        </div>
        <div class="dv-opt" id="1a-off">
            <div class="dv-olabel"><a class="dv-oid" href="#1a">1a</a>Light, lower down: an always-on block, and Developer with fault reports off (its rows dim until it is on).</div>
            <div class="dv-card" data-frame='{"theme":"light","start":"archive"}'></div>
        </div>
        <div class="dv-opt" id="1a-phone">
            <div class="dv-olabel"><a class="dv-oid" href="#1a">1a</a>Phone: the list of groups</div>
            <div class="dv-card" data-frame='{"kind":"phoneList","theme":"dark"}'></div>
        </div>
        <div class="dv-opt" id="1a-phone-group">
            <div class="dv-olabel"><a class="dv-oid" href="#1a">1a</a>Phone: one group</div>
            <div class="dv-card" data-frame='{"kind":"phoneGroup","theme":"light","group":"messages"}'></div>
        </div>
        <div class="dv-opt" id="1a-phone-sheet">
            <div class="dv-olabel"><a class="dv-oid" href="#1a">1a</a>Phone: changing how often</div>
            <div class="dv-card" data-frame='{"kind":"phoneGroup","theme":"dark","group":"messages","sheet":true}'></div>
        </div>
        <div class="dv-opt" id="1b">
            <div class="dv-olabel"><a class="dv-oid" href="#1b">1b</a>Alternative: one group at a time. The left list picks the group; nothing else shows.</div>
            <div class="dv-card" data-frame='{"theme":"dark","start":"skills","single":true}'></div>
        </div>
        <div class="dv-opt" id="1c">
            <div class="dv-olabel"><a class="dv-oid" href="#1c">1c</a>Alternative: one left-list entry per feature, A to Z, with no subject groups.</div>
            <div class="dv-card" data-frame='{"theme":"light","start":"boards","single":true,"flat":true,"current":"Boards"}'></div>
        </div>
    </div>
    <ul class="dv-notes">
        <li><b>Why <a class="dv-oid" href="#1a">1a</a>:</b> the user asked for one settings page with everything in it. One scrolling page lets you read down it, and search narrows that same page. <a class="dv-oid" href="#1b">1b</a> hides everything outside the chosen group, so you have to guess which group holds a setting. <a class="dv-oid" href="#1c">1c</a> makes a left list of more than forty entries that reads like a feature catalogue, not like settings.</li>
        <li><b>Groups:</b> a feature with settings of its own has its own group, such as Messages, Skills or Tickets. Smaller features sit as blocks inside a group named after their subject. Each block keeps its own switch.</li>
        <li><b>Timing:</b> every "Runs every…" setting is a chip such as <i>every 5 tool calls</i>. Clicking it opens the same choices as today's editor: a number, then % of context, tool calls or minutes, or once at an event.</li>
        <li><b>Gone from Settings:</b> every feature's "What it can say to the agent" list. The long feature descriptions shrink to one plain line, and the full help sits behind "How it works".</li>
    </ul>
    <p class="dv-next">Try next: "make the group switch a plain switch, no On label" · "show the Changed filter" · "put Environments and Tunler back as tabs"</p>
</section>

<script>
const M = (n) => ({n, u: "minutes"});
const USES = (n) => ({n, u: "uses"});
const PCT = (n) => ({n, u: "percent"});
const IDLE = {on: "idle"};
const WORKED = {on: "worked"};

const CATALOG = [
    {section: "General", groups: [
        {id: "journal", title: "Journal", hint: "This project's journal and how it starts", rows: [
            {t: "color", label: "Project color", hint: "The band that tells this project apart from other open journals", src: "identity.color", old: "Project color", changed: true},
            {t: "switch", label: "Deliver messages while the agent works", hint: "Off: they are typed into its terminal instead", src: "delivery.channel", old: "Send lines through the channel", on: true},
            {t: "switch", label: "Start the last agent when you write", hint: "Only when no agent is running", src: "starting_agents.wake_on_message", old: "Start the environment's last agent when the user writes and none is running", on: false},
            {t: "switch", label: "Set other hooks aside at launch", hint: "They are put back when the agent exits", src: "clean_slate", on: true},
            {t: "buttons", label: "Chrome extension", hint: "The chat on any page; the agent can see and use the tab", src: "extension", old: "Chrome extension", buttons: ["Add to Chrome", "Download"]},
            {t: "buttons", label: "Services", hint: "Start, stop and read the logs of the journal's processes", src: "services", old: "Services", buttons: ["Show services"]},
            {block: true, label: "Install updates automatically", hint: "Off: the agent installs a new version instead", src: "auto_update", on: true, cad: M(5), rows: []},
            {t: "always", src: "starting_agents", label: "Start agents from the viewer"},
            {t: "always", src: "open_viewer", label: "Open the viewer at launch"},
            {t: "always", src: "browser_control", label: "Let the agent use your browser tab"},
            {t: "always", src: "plugins", label: "Plugins"},
            {t: "danger", label: "Stop the journal", hint: "Closes the viewer, the engine and every plugin. Nothing is deleted.", src: "stop", old: "Stop the journal", buttons: ["Stop"]},
        ]},
        {id: "viewer", title: "Viewer", hint: "What this browser shows", rows: [
            {t: "switch", label: "Show what happened while you were away", hint: "A card when you come back to this tab", src: "viewer.away", old: "While you were away card", on: true},
            {t: "switch", label: "Show the Home tour", src: "viewer.tour_seen", old: "Home tour", on: false},
            {block: true, label: "Show the status bar", src: "status_bar", on: true, rows: [
                {t: "switch", label: "Show how much of the plan is left", src: "status_bar.usage", on: true},
            ]},
            {t: "switch", label: "Show the agent's terminal", src: "terminal", on: true},
            {t: "switch", label: "Connect a phone", hint: "Scan a code to use the chat from your phone", src: "phone", on: true},
            {t: "switch", label: "Show the agent family tree", src: "family_tree", on: true},
            {t: "switch", label: "Show the agent organization", hint: "Domains and roles read from the project", src: "organization", on: true},
        ]},
        {id: "chat", title: "Chat", hint: "What the chat shows", rows: [
            {t: "switch", label: "Show what the agent is thinking", src: "thinking", on: true},
            {t: "switch", label: "Show file edits as they land", src: "file_feed", on: true},
            {t: "switch", label: "Show branch switches", src: "branch_switches", on: true},
            {t: "switch", label: "Show history searches", src: "history_searches", on: true},
            {t: "switch", label: "Hide turns that only acknowledge", hint: "The chat's Shown menu brings them back", src: "acknowledgements", on: true},
            {t: "switch", label: "Pin links the agent gives you", src: "pinned_links", on: true},
            {t: "switch", label: "Pin open pull requests", src: "pull_requests", on: true},
            {t: "switch", label: "Show buttons on the agent's messages", src: "message_buttons", on: true},
            {t: "switch", label: "Turn row numbers into links", hint: "to-do 648 opens to-do 648", src: "row_links", on: true},
            {t: "switch", label: "Show update reports as cards", src: "update_reports", on: true},
            {block: true, label: "Show permission prompts", hint: "Allow and Deny in the chat", src: "permission_prompts", on: true, rows: [
                {t: "switch", label: "Skip permission prompts", hint: "Restarts the agent in the same conversation", src: "permission_prompts.skip", old: "Skip permission prompts", on: false},
            ]},
        ]},
    ]},
    {section: "Agent", groups: [
        {id: "agent", title: "Agent", hint: "How the agent works and talks to you", rows: [
            {block: true, label: "Address you by title", src: "form_of_address", on: true, rows: [
                {t: "text", label: "Title", hint: "Empty: your first name alone", src: "form_of_address.title", value: "Sir"},
                {t: "text", label: "First name", hint: "Empty: the name git knows you by", src: "form_of_address.first_name", value: "", placeholder: "Jesse"},
            ]},
            {block: true, label: "Work modes", src: "work_modes", on: true, rows: [
                {t: "choice", label: "Mode", src: "work_modes.mode", options: ["Builder", "Orchestrator", "Solo"], value: "Builder"},
                {t: "number", label: "Remind an orchestrator after its own edits", src: "work_modes.drift_after", value: 8, unit: "edits"},
            ]},
            {block: true, label: "Keep journal talk out of the chat", src: "chat_etiquette", on: true, rows: [
                {t: "cadence", label: "Remind the agent", src: "chat_etiquette.remind", on: true, cad: {n: 20, u: "notices"}},
            ]},
            {t: "switch", label: "Brief each new session", src: "session_briefing", on: true},
            {block: true, label: "Run commands from tags", hint: "A tag the agent writes runs the command it stands for", src: "command_tags", on: true, rows: [
                {t: "cadence", label: "Show the agent the tag for a command", src: "command_tags.hint", on: true, cad: M(30)},
            ]},
        ]},
        {id: "sessions", title: "Sessions and helpers", hint: "Agent sessions, subagents and helpers", rows: [
            {block: true, label: "Track agent sessions", src: "agent_sessions", on: true, rows: [
                {t: "switch", label: "Hold a session pushed out of its environment", hint: "Its writes wait until it claims the environment back", src: "agent_sessions.eviction", on: true},
                {t: "cadence", label: "Mark silent sessions stopped", src: "agent_sessions.liveness", on: true, cad: M(60)},
                {t: "number", label: "Silent for", src: "agent_sessions.quiet", value: 60, unit: "minutes", indent: true},
                {t: "switch", label: "Release a silent subagent's rows", src: "agent_sessions.subagents", on: true},
                {t: "number", label: "Silent for", src: "agent_sessions.lapse", value: 20, unit: "minutes", indent: true},
                {t: "number", label: "List sessions active in the last", src: "agent_sessions.recent", value: 60, unit: "minutes"},
            ]},
            {t: "switch", label: "Wait for named agents and runs", hint: "Results come back when they all finish", src: "awaited", on: true},
            {t: "switch", label: "Allow helper agents", src: "helpers", on: true},
            {t: "switch", label: "Give each helper its own worktree", src: "helper_worktrees", on: true},
            {t: "switch", label: "Link worktrees to the project's journal", src: "worktrees", on: true},
        ]},
        {id: "long_commands", title: "Long commands", feature: "long_commands", on: true, hint: "Move a command that holds the terminal to the background", rows: [
            {t: "number", label: "Move to the background after", src: "long_commands.after_seconds", value: 30, unit: "seconds"},
            {t: "switch", label: "Tell the agent about commands it left running", hint: "When one ends or has run ten minutes", src: "long_commands.watch runs", on: true},
        ]},
        {id: "laws", title: "Laws", feature: "journal_laws", fixed: true, hint: "Dispatch and reading rules every agent follows", rows: [
            {t: "switch", label: "Name subagents after cartoon characters", src: "journal_laws.cartoon_names", on: false},
            {t: "number", label: "Refuse to read a whole file longer than", src: "journal_laws.whole_read_lines", value: 300, unit: "lines"},
            {t: "number", label: "Keep lines at each end of long output", hint: "0 keeps every line", src: "journal_laws.output_lines", value: 200, unit: "lines"},
            {t: "switch", label: "Name the largest tool result of the session", src: "journal_laws.largest result", on: true},
            {t: "number", label: "Ignore results smaller than", src: "journal_laws.result_floor", value: 20000, unit: "characters", indent: true},
            {t: "cadence", label: "Warn when an instruction file is too long", src: "journal_laws.too long", on: true, cad: M(1440)},
            {t: "cadence", label: "Repeat a law when its keyword comes up", src: "journal_laws.whisper", on: true, cad: USES(100)},
        ]},
        {id: "skills", title: "Skills", feature: "skill_loading", on: true, hint: "Which skills load, and when", rows: [
            {t: "timing", label: "Remind the agent to load the journal skill", src: "skill_loading", cad: USES(25)},
            {t: "switch", label: "Load a skill when its keyword comes up", src: "skill_loading.keywords", on: true},
            {t: "switch", label: "Show each loaded skill in the chat", src: "skill_loading.chat", on: true},
            {t: "switch", label: "Hold writes after a compaction until the skill is loaded", src: "skill_loading.reload", on: true},
            {t: "switch", label: "Hold tool calls until every-start skills are loaded", src: "skill_loading.always", on: true},
            {t: "switch", label: "Reload every-start skills that changed", src: "skill_loading.stale", on: true},
            {t: "number", label: "Refuse tool calls at most", src: "skill_loading.most_refusals", value: 5, unit: "times"},
            {t: "number", label: "Then let tool calls through for", src: "skill_loading.steps_aside", value: 10, unit: "tool calls"},
            {t: "number", label: "After a compaction, also reload the last used", src: "skill_loading.recent", value: 5, unit: "skills"},
        ]},
    ]},
    {section: "Work", groups: [
        {id: "work_tracking", title: "Work tracking", feature: "work_tracking", on: true, hint: "Writes need open work; work is logged against its to-do", rows: [
            {t: "timing", label: "Check open work", src: "work_tracking", cad: WORKED},
            {t: "cadence", label: "Work the list without asking", src: "work_tracking.auto", on: true, cad: IDLE},
            {t: "cadence", label: "Tell a stopped agent to carry on", src: "work_tracking.carry on", on: true, cad: M(10)},
            {t: "number", label: "Hold writes after edits without a log entry", src: "work_tracking.log_after", value: 20, unit: "edits"},
            {t: "number", label: "Name the work in hand every", src: "work_tracking.name_work_every", value: 10, unit: "edits"},
            {t: "number", label: "Check what the agent waits on every", src: "work_tracking.ask_awaiting_every", value: 5, unit: "minutes"},
            {t: "number", label: "Recheck a blocked to-do every", src: "work_tracking.ask_blocked_every", value: 1, unit: "closed to-dos"},
            {block: true, label: "Catch work put off for later", hint: "Said once when a later has no to-do", src: "put_off_work", on: true, cad: IDLE, rows: []},
            {block: true, label: "Close to-dos from commit messages", hint: "Journal: todos done 648", src: "close_from_commits", on: true, rows: []},
        ]},
        {id: "plans", title: "Plans", feature: "plans", on: true, hint: "A plan moves on as its rows close", rows: [
            {t: "cadence", label: "Nudge an agent that stalls on a running plan", src: "plans.still", on: true, cad: M(5)},
            {t: "cadence", label: "Recheck a running plan's blocked to-dos", src: "plans.blocked", on: true, cad: M(30)},
        ]},
        {id: "questions", title: "Questions", feature: "ask_questions", fixed: true, hint: "Choices only you can make are asked as questions", rows: [
            {t: "cadence", label: "Hold writes until choices are asked as a question", src: "ask_questions.asking", on: true, cad: IDLE},
            {t: "cadence", label: "Recheck questions open longer than a day", src: "ask_questions.settled", on: true, cad: M(1440)},
            {t: "number", label: "Undo a picked answer within", src: "ask_questions.hold", value: 3, unit: "seconds"},
        ]},
        {id: "messages", title: "Messages", feature: "messages", on: true, hint: "Your messages are named, answered and closed", rows: [
            {t: "cadence", label: "Name unread messages", src: "messages.unread", on: true, cad: USES(5), changed: true},
            {t: "number", label: "Hold writes after naming them", src: "messages.unread.patience", value: 5, unit: "times", indent: true},
            {t: "cadence", label: "Require a reply before writing", src: "messages.answering", on: true, cad: USES(10)},
            {t: "number", label: "Stop asking after", src: "messages.answering.patience", value: 3, unit: "times", indent: true},
            {t: "switch", label: "Close a message once it is handled", src: "messages.closing", on: true},
            {t: "switch", label: "Link new rows to the open message", src: "messages.linking", on: true},
            {t: "cadence", label: "Remind to keep paragraphs apart", src: "messages.paragraphs", on: true, cad: IDLE},
            {t: "switch", label: "Remind to name what a number is", hint: "answered 1712 becomes answered message 1712", src: "messages.numbers", on: true},
        ]},
        {id: "sequences", title: "Sequences", feature: "sequences", on: true, hint: "Steps the agent follows in order", rows: [
            {t: "cadence", label: "Remind about an unfinished step", src: "sequences.unfinished + sequences.nudge_every", on: true, cad: M(1)},
        ]},
        {id: "boards", title: "Boards", feature: "boards", on: true, hint: "Boards of tickets with their own stages", rows: [
            {t: "cadence", label: "Suggest new work", hint: "Shown as chips under New work", src: "boards.ideas", on: true, cad: M(720)},
            {t: "choice", label: "Board filler model", src: "boards.filler_model", options: ["Haiku", "Sonnet", "Opus"], value: "Sonnet"},
            {t: "choice", label: "Reviewer model", src: "boards.reviewer_model", options: ["Haiku", "Sonnet", "Opus"], value: "Sonnet"},
            {t: "switch", label: "This environment orchestrates its boards", hint: "On: its agent only delegates", src: "boards.orchestrating", on: false},
            {block: true, label: "Show to-dos as a kanban board", src: "kanban", on: true, rows: [
                {t: "number", label: "Keep done cards for", src: "kanban.done_days", value: 7, unit: "days"},
            ]},
        ]},
        {id: "tickets", title: "Tickets", feature: "tickets", on: true, hint: "Each ticket runs in an environment of its own", rows: [
            {t: "timing", label: "Check the tickets", src: "tickets", cad: M(1)},
            {t: "number", label: "Tickets running at once", hint: "0: no limit", src: "tickets.running", value: 2, unit: "tickets", changed: true},
            {t: "number", label: "Repeat that a ticket's plan is done every", src: "tickets.remind_every", value: 5, unit: "minutes"},
            {t: "cadence", label: "Remind the orchestrator of a ticket's open question", src: "tickets.asks", on: true, cad: M(5)},
            {t: "cadence", label: "Remind it of a ticket waiting on a person", src: "tickets.awaits", on: true, cad: M(5)},
            {t: "cadence", label: "Remind it of decisions it may take", src: "tickets.decisions", on: true, cad: M(5)},
            {t: "cadence", label: "Ask an idle orchestrator to check its boards", src: "tickets.board check", on: true, cad: M(5)},
            {block: true, label: "Run each ticket's own app", hint: "From its worktree, while it is worked on", src: "hosting", on: true, cad: M(1), rows: []},
        ]},
    ]},
    {section: "Memory and records", groups: [
        {id: "memory", title: "Memory", hint: "Facts, rules and reminders said again to the agent", rows: [
            {block: true, label: "Memory checkpoints", src: "memory_checkpoints", on: true, cad: {at: [50, 70, 90, 95]}, rows: [
                {t: "cadence", label: "Reread every rule and fact each week", src: "memory_checkpoints.rereading", on: true, cad: M(1440)},
            ]},
            {block: true, label: "Repeat facts", src: "facts", on: true, cad: PCT(25), rows: [
                {t: "cadence", label: "Repeat a fact when its keyword comes up", src: "facts.whisper", on: true, cad: USES(100)},
            ]},
            {block: true, label: "Repeat reminders", src: "reminders", on: true, cad: PCT(25), rows: [
                {t: "cadence", label: "Repeat a reminder when its keyword comes up", src: "reminders.whisper", on: true, cad: USES(100)},
            ]},
            {block: true, label: "Repeat rules", src: "rules", on: true, cad: PCT(25), rows: [
                {t: "cadence", label: "Repeat a rule when its keyword comes up", src: "rules.whisper", on: true, cad: USES(100)},
            ]},
        ]},
        {id: "records", title: "Records", hint: "Documents, collections and checks on the record", rows: [
            {block: true, label: "Check the record for rows that no longer hold", src: "record_audit", on: true, cad: M(1440), rows: []},
            {block: true, label: "Ask for source links", hint: "On new plans, documents and reports", src: "source_links", on: true, rows: [
                {t: "number", label: "Count what was read in the last", src: "source_links.within", value: 30, unit: "minutes"},
            ]},
            {block: true, label: "Keep document revisions", src: "revisions", on: true, rows: [
                {t: "number", label: "Save an open revision after no edits for", src: "revisions.keep_after_minutes", value: 30, unit: "minutes"},
            ]},
            {block: true, label: "Read attachments for the agent", src: "attachment_descriptions", on: true, rows: [
                {t: "switch", label: "Ask for a description of each image and video", src: "attachment_descriptions.tagging", on: true},
                {t: "switch", label: "Sample videos into frames", hint: "Needs ffmpeg", src: "attachment_descriptions.frames", on: true},
            ]},
            {block: true, label: "Triggers", hint: "Your watch words and what they do", src: "triggers", on: true, rows: [
                {t: "switch", label: "Fire a trigger when its word comes up", src: "triggers.watching", on: true},
            ]},
            {t: "switch", label: "Collections", src: "collections", on: true},
            {t: "switch", label: "Sort dumped files into a collection", src: "dumps", on: true},
            {t: "switch", label: "Templates", src: "templates", on: true},
            {t: "switch", label: "Turn accepted suggestions into to-dos", src: "suggestions", on: true},
            {t: "switch", label: "Checks", hint: "Pass or fail scripts; a failure is filed", src: "checks", on: true},
        ]},
        {id: "archive", title: "Archive and cleanup", hint: "How long finished rows and runtime files stay", rows: [
            {block: true, label: "Archive finished rows", src: "auto_archive", on: true, cad: M(60), rows: [
                {t: "number", label: "Keep reports for", hint: "0 keeps them listed", src: "auto_archive.keep.report", old: "Keep: reports", value: 14, unit: "days"},
                {t: "number", label: "Keep finished to-dos for", src: "auto_archive.keep.todo", old: "Keep: todos", value: 7, unit: "days"},
            ]},
            {block: true, label: "Clean the runtime folder", src: "runtime_cleanup", fixed: true, cad: M(60), rows: [
                {t: "number", label: "Remove a quiet session's files after", src: "runtime_cleanup.days", value: 2, unit: "days"},
            ]},
        ]},
        {id: "sharing", title: "Sharing", feature: "sharing", on: true, hint: "Share one item through a tunler link", rows: [
            {t: "text", label: "Tunler server", src: "sharing.host", value: "tunler.jessegall.nl", wide: true},
        ]},
    ]},
    {section: "More", groups: [
        {id: "developer", title: "Developer", hint: "For working on the journal itself", rows: [
            {block: true, label: "Report developer faults", src: "dev_faults", on: false, rows: [
                {t: "switch", label: "Report anything slower than 50 ms", src: "dev_faults.budget", on: true},
                {t: "switch", label: "Report errors the viewer throws", src: "dev_faults.console", on: true},
                {t: "switch", label: "Write a diagnostic log", hint: ".journal/runtime/diagnostics.log", src: "dev_faults.log", on: false},
            ]},
            {block: true, label: "Critique rounds", src: "critique", on: true, rows: [
                {t: "text", label: "App address", src: "critique.app", value: "", placeholder: "http://127.0.0.1:8650", wide: true},
                {t: "text", label: "Login file", src: "critique.login", value: "", placeholder: "Browser storage file", wide: true},
                {t: "text", label: "Demo data command", src: "critique.seed", value: "", placeholder: "Runs before each round", wide: true},
                {t: "text", label: "Playwright folder", src: "critique.browsers", value: "", placeholder: "Folder with Playwright installed", wide: true},
            ]},
            {t: "switch", label: "Allow session recording", src: "session_recording", on: true},
        ]},
        {id: "environments", title: "Environments", link: true, hint: "Opens the list of environments"},
        {id: "tunler", title: "Tunler", link: true, hint: "Opens the tunler account and its domains"},
    ]},
];

const UNIT_WORDS = {percent: "% of context", uses: "tool calls", minutes: "minutes", notices: "journal lines"};
const EVENT_WORDS = {idle: "when the agent rests", worked: "after the agent works", start: "at session start"};

function cadWords(c) {
    if (!c) return "";
    if (c.on) return EVENT_WORDS[c.on];
    if (c.at) return `at ${c.at.join(", ")}% of context`;
    if (c.u === "percent") return `every ${c.n}% of context`;
    if (c.u === "minutes") {
        if (c.n === 1) return "every minute";
        if (c.n === 60) return "every hour";
        if (c.n === 1440) return "every day";
        if (c.n % 60 === 0) return `every ${c.n / 60} hours`;
    }
    return `every ${c.n} ${UNIT_WORDS[c.u]}`;
}



const V2 = {
    "messages.unread.patience": {label: "Pause writes after naming them"},
    "skill_loading.reload": {label: "Pause writes after a compaction until the skill is loaded"},
    "skill_loading.always": {label: "Pause tool calls until every-start skills are loaded"},
    "work_tracking.log_after": {label: "Pause writes after edits without a log entry"},
    "ask_questions.asking": {label: "Pause writes until choices are asked as a question"},
    "agent_sessions.eviction": {label: "Pause a session pushed out of its environment"},
    "skill_loading.most_refusals": {label: "Refuse tool calls at most", hint: "Then they go through for a while"},
    journal: {hint: "For the whole project, every environment"},
    viewer: {hint: "For this browser only"},
    messages: {hint: "How the agent handles the messages you send"},
    plans: {hint: "How the agent moves through a plan"},
    work_tracking: {hint: "The agent opens work before it edits and logs it on its to-do"},
    boards: {hint: "Ticket boards and the agents that fill them"},
};
const tx = (r, o, key) => (o.v === 2 && V2[r.src || r.id] && V2[r.src || r.id][key]) || r[key];

const DEFAULTS = {"messages.unread": "every 3 tool calls", "tickets.running": "3", "identity.color": "the color from the project name"};

const esc = (s) => String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

function hl(text, query) {
    let out = esc(text);
    if (!query) return out;
    for (const word of query.toLowerCase().split(/\s+/).filter(Boolean)) {
        out = out.replace(new RegExp(`(${word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "ig"), "<mark>$1</mark>");
    }
    return out;
}

const hits = (text, query) => !query || query.toLowerCase().split(/\s+/).filter(Boolean).every((w) => String(text).toLowerCase().includes(w));
const groups = () => CATALOG.flatMap((s) => s.groups.map((g) => ({...g, section: s.section})));

function prune(group, query, o = {}) {
    if (!query || hits(group.title, query)) return group;
    const keep = (rows) =>
        rows.flatMap((r) => {
            if (r.block) {
                if (hits(r.label + " " + (r.hint || ""), query)) return [r];
                const inner = keep(r.rows);
                return inner.length ? [{...r, rows: inner}] : [];
            }
            return hits(`${tx(r, o, "label")} ${tx(r, o, "hint") || ""}`, query) ? [r] : [];
        });
    const rows = keep(group.rows || []);
    return rows.length ? {...group, rows} : null;
}

function countRows(rows) {
    return rows.reduce((n, r) => n + (r.block ? 1 + countRows(r.rows) : 1), 0);
}

function everyRow() {
    const out = [];
    const walk = (rows) => rows.forEach((r) => (out.push(r), r.rows && walk(r.rows)));
    groups().forEach((g) => (out.push(g), walk(g.rows || [])));
    return out;
}

const changedCount = () => everyRow().filter((r) => r.changed).length;
const offCount = () => everyRow().filter((r) => r.on === false).length;

const sw = (on) => `<span class="sw ${on ? "on" : ""}"><i></i></span>`;
const fsw = (on) => `<span class="fsw">${sw(on)}<span>${on ? "On" : "Off"}</span></span>`;
const caret = `<i class="caret"></i>`;

function cadChip(c, open) {
    return `<button class="cad ${open ? "open" : ""}">${esc(cadWords(c))}${caret}</button>`;
}

function popover(row, o = {}) {
    const c = row.cad;
    const unit = c.on || c.u;
    const opt = (key, label) => `<span class="${unit === key ? "on" : ""}">${label}</span>`;
    return `<div class="pop">
        <div class="pop-t">${esc(row.label)}</div>
        <div class="pop-r"><span class="num">Every <span class="field n">${c.n ?? ""}</span></span>
            <span class="seg">${opt("percent", "% of context")}${opt("uses", "tool calls")}${opt("minutes", "minutes")}</span></div>
        <div class="pop-r"><span class="num">${o.v === 2 ? "Or once, when" : "Or once"}</span>
            <span class="seg">${opt("idle", "the agent rests")}${opt("worked", "after work")}${opt("start", "a session starts")}</span></div>
        <div class="pop-f"><span>Shipped: ${esc(DEFAULTS[row.src] || cadWords(c))}</span><button class="reset">Reset</button></div>
    </div>`;
}

function control(r, o) {
    switch (r.t) {
        case "switch":
            return sw(r.on);
        case "timing":
            return cadChip(r.cad, o.pop === r.src) + (o.pop === r.src ? popover(r, o) : "");
        case "cadence":
            return cadChip(r.cad, o.pop === r.src) + sw(r.on) + (o.pop === r.src ? popover(r, o) : "");
        case "number":
            return `<span class="num"><span class="field n">${esc(r.value)}</span>${esc(r.unit)}</span>`;
        case "text":
            return `<span class="field ${r.wide ? "w" : "t"}">${r.value ? esc(r.value) : `<span class="ph">${esc(r.placeholder || "")}</span>`}</span>`;
        case "choice":
            return `<span class="seg">${r.options.map((x) => `<span class="${x === r.value ? "on" : ""}">${esc(x)}</span>`).join("")}</span>`;
        case "color":
            return `<span class="swatch"></span>`;
        case "buttons":
        case "danger":
            return r.buttons.map((b) => `<button class="btn ${r.t === "danger" ? "danger" : ""}">${esc(b)}</button>`).join("");
        default:
            return "";
    }
}

function row(r, o, extra = "") {
    const dot = r.changed ? `<span class="cdot" title="Changed"></span>` : "";
    const reset = r.changed && o.hover === r.src ? `<button class="reset">Reset</button>` : "";
    const cls = ["row", extra, r.indent ? "ind" : "", o.dim ? "dimmed" : ""].join(" ");
    return `<div class="${cls}">
        <div class="lab"><div class="l1">${dot}<span>${hl(tx(r, o, "label"), o.query)}</span></div>${tx(r, o, "hint") ? `<div class="hint">${hl(tx(r, o, "hint"), o.query)}</div>` : ""}</div>
        <div class="ctl">${reset}${control(r, o)}</div>
    </div>`;
}

function block(b, o) {
    const head = `<div class="row head ${o.dim ? "dimmed" : ""}">
        <div class="lab"><div class="l1"><span>${hl(b.label, o.query)}</span>${o.v === 2 ? `<button class="how sm">How it works</button>` : `<button class="info" title="How it works">?</button>`}</div>${b.hint ? `<div class="hint">${hl(b.hint, o.query)}</div>` : ""}</div>
        <div class="ctl">${b.cad ? cadChip(b.cad) : ""}${b.fixed ? `<span class="pill">Always on</span>` : sw(b.on)}</div>
    </div>`;
    const inner = {...o, dim: o.dim || b.on === false};
    return head + b.rows.map((r) => row(r, inner, "child")).join("");
}

function card(rows, o) {
    const always = rows.filter((r) => r.t === "always");
    const danger = rows.filter((r) => r.t === "danger");
    const main = rows.filter((r) => r.t !== "always" && r.t !== "danger");
    let html = `<div class="card">${main.map((r) => (r.block ? block(r, o) : row(r, o))).join("")}`;
    if (always.length) html += `<div class="always"><b>Always on</b>${always.map((r) => `<span>${esc(r.label)}</span>`).join("<span>·</span>")}</div>`;
    html += `</div>`;
    if (danger.length) html += `<div class="card danger-card">${danger.map((r) => row(r, o)).join("")}</div>`;
    return html;
}

function group(g, o) {
    if (g.link) {
        return `<section><div class="card">${row({label: g.title, hint: g.hint, t: "buttons", buttons: ["Open " + g.title]}, o)}</div></section>`;
    }
    const off = g.feature && g.on === false;
    const gsw = o.v === 2 ? `<span class="gsw"><span>${g.on ? "On" : "Off"}</span>${sw(g.on)}</span>` : fsw(g.on);
    const right = g.feature ? (g.fixed ? `<span class="pill">Always on</span>` : gsw) : "";
    const how = g.feature ? `<button class="how">How it works ${caret}</button>` : "";
    return `<section>
        <div class="gh ${o.v === 2 ? "v2" : ""}"><div class="gh-t"><h2>${hl(g.title, o.query)}</h2><p>${off ? "Off. Turn it on to change these." : esc(tx(g, o, "hint"))}${how}</p></div>
            <div class="gh-c">${right}</div></div>
        ${card(g.rows, {...o, dim: off})}
    </section>`;
}

function pageBar(o) {
    const q = o.query
        ? `<span class="find active"><span class="lens"></span><span class="q">${esc(o.query)}</span><span class="clear">Clear</span></span>`
        : `<span class="find"><span class="lens"></span><span class="q">Find a setting</span><span class="kbd">/</span></span>`;
    const f = o.filter || "all";
    return `<div class="pbar">${q}
        <span class="seg"><span class="${f === "all" ? "on" : ""}">All</span><span class="${f === "changed" ? "on" : ""}">${o.v === 2 ? `<i class="cdot"></i>` : ""}Changed <em>${changedCount()}</em></span><span class="${f === "off" ? "on" : ""}">Off <em>${offCount()}</em></span></span>
        <span class="scope">${o.v === 2 ? "Applies to environment" : "Feature settings apply to"} <span class="env">main</span></span>
    </div>`;
}

function nav(o, shown) {
    return `<nav class="nav">${CATALOG.map((s) => {
        const items = s.groups
            .map((g) => {
                const found = shown ? shown.find((x) => x.id === g.id) : g;
                if (shown && !found) return "";
                const changed = everyRow().some((r) => r.changed) && JSON.stringify(g).includes('"changed":true');
                const mark = g.link
                    ? `<span class="out">↗</span>`
                    : shown
                      ? `<span class="n">${countRows(found.rows)}</span>`
                      : g.feature && g.on === false
                        ? `<span class="off">Off</span>`
                        : changed
                          ? `<span class="cdot"></span>`
                          : "";
                return `<div class="ni ${o.current === g.id ? "on" : ""}"><span class="nm">${esc(g.title)}</span>${mark}</div>`;
            })
            .join("");
        return items ? `<div class="nav-sec">${esc(s.section)}</div>${items}` : "";
    }).join("")}</nav>`;
}

function shell(inner, theme) {
    const items = (list, on) => list.map((x) => `<div class="side-item ${x === on ? "on" : ""}">${x}</div>`).join("");
    return `<div class="shell"><aside class="side">
        <div class="side-head"><span class="badge">A</span>acme-shop</div>
        ${items(["Hub"])}
        <div class="side-sec">Environment</div>${items(["Home", "Plans", "To-dos", "Collections"])}
        <div class="side-sec">Project</div>${items(["Board", "Documents", "Sequences", "Checks", "Plugins", "Settings"], "Settings")}
        <div class="side-sec">Environments</div>${items(["main", "checkout-fix"])}
        <div class="side-foot">Agent journal 2.231.0</div>
    </aside><div class="main"><div class="top">acme-shop <span>/</span> main <span>/</span> <b>Settings</b></div>${inner}</div></div>`;
}

function desktop(o) {
    const all = groups();
    let list = all;
    let lead = "";
    if (o.query) {
        list = all.map((g) => prune(g, o.query, o)).filter(Boolean);
        const n = list.reduce((k, g) => k + countRows(g.rows || []), 0);
        lead = `<div class="found">${n} settings in ${list.length} groups match “${esc(o.query)}”</div>`;
    } else if (o.single) {
        list = all.filter((g) => g.id === o.start);
    } else if (o.start) {
        list = all.slice(all.findIndex((g) => g.id === o.start));
    }
    const current = o.query ? list[0]?.id : o.start || "journal";
    const navHtml = o.flat ? flatNav(o) : nav({current}, o.query ? list : null);
    const body = `${pageBar(o)}<div class="sbody">${navHtml}<div class="content"><div class="col">${lead}${list.map((g) => group(g, o)).join("")}</div></div></div>`;
    return `<div class="scr th-${o.theme}" style="width:${o.w || 1440}px;height:${o.h || 900}px">${shell(body, o.theme)}</div>`;
}

function flatNav(o) {
    const names = [];
    groups().forEach((g) => {
        if (g.feature || ["journal", "viewer"].includes(g.id)) names.push(g.title);
        (g.rows || []).forEach((r) => {
            if (r.block) names.push(r.label);
            else if (r.t === "switch" && r.src && !r.src.includes(".")) names.push(r.label);
        });
    });
    names.sort((a, b) => a.localeCompare(b));
    return `<nav class="nav"><div class="nav-sec">${names.length} entries, A to Z</div>${names.map((n) => `<div class="ni ${n === o.current ? "on" : ""}"><span class="nm">${esc(n)}</span></div>`).join("")}</nav>`;
}

function phoneList(o) {
    const sections = CATALOG.map((s) => `<div class="ph-sec">${esc(s.section)}</div><div class="ph-card">${s.groups
        .map((g) => {
            const mark = g.feature && g.on === false ? `<span class="off">Off</span>` : JSON.stringify(g).includes('"changed":true') ? `<span class="cdot"></span>` : "";
            return `<div class="ph-item"><span class="nm">${esc(g.title)}</span>${mark}<span class="chev-r"></span></div>`;
        })
        .join("")}</div>`).join("");
    return phone(o, `<div class="ph-bar"><span class="ph-title">Settings</span></div>
        <div class="ph-pad"><span class="find"><span class="lens"></span><span class="q">Find a setting</span></span>
        <span class="seg"><span class="on">All</span><span>Changed <em>${changedCount()}</em></span><span>Off <em>${offCount()}</em></span></span></div>
        <div class="ph-list">${sections}</div>`);
}

function phoneRow(r, o = {}) {
    const dot = r.changed ? `<span class="cdot"></span>` : "";
    const lab = `<div class="lab"><div class="l1">${dot}${esc(tx(r, o, "label"))}</div>${tx(r, o, "hint") ? `<div class="hint">${esc(tx(r, o, "hint"))}</div>` : ""}${r.t === "cadence" ? cadChip(r.cad) : ""}</div>`;
    const ctl = r.t === "cadence" ? sw(r.on) : control(r, {});
    return `<div class="row ${r.indent ? "ind" : ""}">${lab}<div class="ctl">${ctl}</div></div>`;
}

function phoneGroup(o) {
    const g = groups().find((x) => x.id === o.group);
    const sheet = o.sheet
        ? `<div class="sheet-scrim"></div><div class="sheet"><span class="grab"></span><h3>Name unread messages</h3>
            <span class="num" style="font-size:14px">Every <span class="field n">5</span></span>
            <span class="seg"><span>% of context</span><span class="on">tool calls</span><span>minutes</span></span>
            <span class="num" style="font-size:14px">Or once, when</span>
            <span class="seg"><span>the agent rests</span><span>after work</span><span>a session starts</span></span>
            <span class="pop-f" style="font-size:13px"><span>Shipped: every 3 tool calls</span><button class="reset" style="font-size:13px !important">Reset</button></span>
            <button class="btn primary">Done</button></div>`
        : "";
    return phone(o, `<div class="ph-bar"><span class="ph-back"><span class="chev-l"></span>Settings</span></div>
        <div class="ph-bar"><span class="ph-title">${esc(g.title)}</span><span style="margin-left:auto">${fsw(g.on)}</span></div>
        <div class="ph-gh">${esc(tx(g, o, "hint"))}</div>
        <div class="ph-list ph-g"><div class="ph-card">${g.rows.map((r) => phoneRow(r, o)).join("")}</div></div>${sheet}`);
}

function phone(o, inner) {
    return `<div class="scr th-${o.theme}" style="width:390px;height:844px"><div class="ph-scr">
        <div class="ph-status"><span>9:41</span><span>5G</span></div>${inner}</div></div>`;
}

const RENDER = {desktop, phoneList, phoneGroup};
document.querySelectorAll("[data-frame]").forEach((el) => {
    const o = JSON.parse(el.dataset.frame);
    el.innerHTML = RENDER[o.kind || "desktop"](o);
});
</script>
</body>
</html>
