<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ERP-UI Screen Patterns</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root{
--paper:#F5F2EC; --surface:#FFFFFF; --ink:#1C1A17; --ink-2:#4A453E; --ink-3:#5E5850;
--line:#D9D2C6; --line-2:#E8E2D8; --wire:#ECE7DE; --wire-2:#DED7CB;
--accent:#A3401C; --accent-ink:#FFFFFF; --accent-soft:#F6E3D6;
--focus:#1F5FD1;
--t-neutral-bg:#ECE8E1; --t-neutral-fg:#3D3934;
--t-info-bg:#E1E9F4; --t-info-fg:#1F4E86;
--t-warm-bg:#F6E3D6; --t-warm-fg:#8A3A12;
--t-good-bg:#DFEEDD; --t-good-fg:#2A5C29;
--t-muted-bg:#EEEBE6; --t-muted-fg:#55504A;
--t-wait-bg:#F5EBC8; --t-wait-fg:#664E00;
--t-alert-bg:#F8E0DE; --t-alert-fg:#9B1C1C;
--display:"Instrument Serif", Georgia, serif;
--text-serif:"Plus Jakarta Sans", system-ui, sans-serif;
--sans:"Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
--mono:"Geist Mono", ui-monospace, monospace;
color-scheme: light;
}
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
--paper:#15130F; --surface:#1E1B16; --ink:#F1ECE3; --ink-2:#C9C1B4; --ink-3:#B3AB9E;
--line:#3E382F; --line-2:#2E2A23; --wire:#29251F; --wire-2:#3A352D;
--accent:#E9875C; --accent-ink:#1A0E07; --accent-soft:#3A2418;
--focus:#8AB4FF;
--t-neutral-bg:#2C2823; --t-neutral-fg:#E3DDD3;
--t-info-bg:#1B2A3E; --t-info-fg:#A9C8F0;
--t-warm-bg:#3A2418; --t-warm-fg:#F2B894;
--t-good-bg:#1D3220; --t-good-fg:#A6D8A2;
--t-muted-bg:#27241F; --t-muted-fg:#C2BBB0;
--t-wait-bg:#352C12; --t-wait-fg:#EBD27F;
--t-alert-bg:#3D1C1A; --t-alert-fg:#F5AAA4;
color-scheme: dark;
}
}
:root[data-theme="dark"]{
--paper:#15130F; --surface:#1E1B16; --ink:#F1ECE3; --ink-2:#C9C1B4; --ink-3:#B3AB9E;
--line:#3E382F; --line-2:#2E2A23; --wire:#29251F; --wire-2:#3A352D;
--accent:#E9875C; --accent-ink:#1A0E07; --accent-soft:#3A2418;
--focus:#8AB4FF;
--t-neutral-bg:#2C2823; --t-neutral-fg:#E3DDD3;
--t-info-bg:#1B2A3E; --t-info-fg:#A9C8F0;
--t-warm-bg:#3A2418; --t-warm-fg:#F2B894;
--t-good-bg:#1D3220; --t-good-fg:#A6D8A2;
--t-muted-bg:#27241F; --t-muted-fg:#C2BBB0;
--t-wait-bg:#352C12; --t-wait-fg:#EBD27F;
--t-alert-bg:#3D1C1A; --t-alert-fg:#F5AAA4;
color-scheme: dark;
}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--paper);color:var(--ink);font:15px/1.55 var(--sans);overflow-x:hidden}
a{color:var(--ink);text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px}
:focus-visible{outline:3px solid var(--focus);outline-offset:2px;border-radius:4px}
.wrap{max-width:1200px;margin:0 auto;padding:0 32px}
@media (max-width:640px){.wrap{padding:0 16px}}
header.top{border-bottom:1px solid var(--line);padding:18px 0}
header.top .wrap{display:flex;align-items:center;justify-content:space-between;gap:12px}
.brand{font:500 13px/1 var(--sans);letter-spacing:.12em;text-transform:uppercase}
.toggle{font:500 13px var(--sans);min-height:44px;padding:0 14px;border:1px solid var(--line);background:var(--surface);color:var(--ink);border-radius:999px;cursor:pointer}
.label{font:500 11px/1.3 var(--sans);letter-spacing:.14em;text-transform:uppercase;color:var(--ink-2)}
h1,h2,h3{font-family:var(--display);font-weight:400;letter-spacing:-.01em;margin:0}
h1{font-size:clamp(40px,6vw,64px);line-height:1.02}
h2{font-size:clamp(30px,4vw,40px);line-height:1.1}
h3{font-size:26px;line-height:1.15}
.lede{font:400 19px/1.5 var(--text-serif);color:var(--ink-2);max-width:62ch;margin:14px 0 0}
p{max-width:72ch}
section.block{padding:56px 0;border-bottom:1px solid var(--line)}
.sec-head{display:flex;gap:16px;align-items:baseline;flex-wrap:wrap;margin-bottom:8px}
.sec-head .n{font:400 14px var(--mono);color:var(--ink-2)}
.intro{color:var(--ink-2);margin:6px 0 28px}
/* rules strip */
.rules{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,250px),1fr));gap:0;border-top:1px solid var(--line);margin-top:32px}
.rules div{padding:18px 18px 18px 0;border-bottom:1px solid var(--line)}
.rules b{display:block;font:400 22px/1.2 var(--display);margin:6px 0 4px}
.rules span{color:var(--ink-2);font-size:14px}
/* tones */
.pill{display:inline-flex;align-items:center;gap:6px;font:500 12.5px/1 var(--sans);padding:5px 10px;border-radius:999px;white-space:nowrap;background:var(--t-neutral-bg);color:var(--t-neutral-fg)}
.pill i{font-style:normal;font-size:11px;line-height:1}
.t-info{background:var(--t-info-bg);color:var(--t-info-fg)}
.t-warm{background:var(--t-warm-bg);color:var(--t-warm-fg)}
.t-good{background:var(--t-good-bg);color:var(--t-good-fg)}
.t-muted{background:var(--t-muted-bg);color:var(--t-muted-fg)}
.t-wait{background:var(--t-wait-bg);color:var(--t-wait-fg)}
.t-alert{background:var(--t-alert-bg);color:var(--t-alert-fg)}
.tones{display:flex;flex-wrap:wrap;gap:8px;margin:12px 0}
/* wireframe primitives */
.wf{background:var(--paper);border:1px solid var(--line);border-radius:10px;padding:18px;font-size:13.5px;position:relative}
.wf .kick{font:500 10.5px/1.2 var(--sans);letter-spacing:.14em;text-transform:uppercase;color:var(--ink-2)}
.wf .ttl{font:400 28px/1.08 var(--display);margin:4px 0 0}
.wf .sub{color:var(--ink-2);font-size:13px}
.wf hr{border:0;border-top:1px solid var(--line);margin:14px 0}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;padding:0 14px;border-radius:6px;border:1px solid var(--line);background:var(--surface);color:var(--ink);font:500 13px var(--sans);white-space:nowrap}
.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}
.btn.blocked{background:var(--surface);color:var(--ink-2);border:1px dashed var(--ink-3)}
.touch .btn{min-height:44px}
.qlink{color:var(--ink-2);font-size:13px;text-decoration:underline;text-underline-offset:3px;white-space:nowrap}
.av{display:inline-grid;place-items:center;width:28px;height:28px;border-radius:50%;background:var(--ink);color:var(--paper);font:600 10.5px var(--sans);flex:none}
.av.lg{width:36px;height:36px;font-size:12px}
.av.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--ink-3)}
.avs{display:inline-flex}.avs .av{border:2px solid var(--paper);margin-left:-6px}.avs .av:first-child{margin-left:0}
.box{background:var(--wire);border-radius:6px}
.row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.between{justify-content:space-between}
.grow{flex:1 1 auto;min-width:0}
.muted{color:var(--ink-2)}
.small{font-size:12.5px}
.mono{font-family:var(--mono);font-size:12px}
.seg{display:inline-flex;border:1px solid var(--line);border-radius:999px;padding:3px;background:var(--surface)}
.seg span{padding:5px 12px;border-radius:999px;font:500 12.5px var(--sans);color:var(--ink-2)}
.seg span.on{background:var(--ink);color:var(--paper)}
.tabs{display:flex;gap:18px;border-bottom:1px solid var(--line);font-size:13px;overflow-x:auto}
.tabs span{padding:8px 0;color:var(--ink-2);white-space:nowrap}
.tabs span.on{color:var(--ink);border-bottom:2px solid var(--ink)}
.tabs sup{font:400 11px var(--mono);color:var(--ink-2);margin-left:3px}
/* figures */
.figs{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.figs>div{padding:12px 12px 12px 0}
.figs b{display:block;font:400 30px/1.05 var(--display);margin:4px 0 2px}
.figs small{color:var(--ink-2);font-size:12px;display:block}
@media (max-width:560px){.figs{grid-template-columns:repeat(2,minmax(0,1fr))}}
/* stepper */
.stepper{display:flex;list-style:none;margin:0;padding:0;flex:1 1 320px;min-width:0}
.stepper li{flex:1;text-align:center;position:relative;font-size:12.5px;color:var(--ink-2);padding-top:22px;min-width:0}
.stepper li::before{content:"";position:absolute;top:8px;left:-50%;right:50%;height:2px;background:var(--line)}
.stepper li:first-child::before{display:none}
.stepper li.done::before,.stepper li.cur::before{background:var(--ink)}
.stepper .dot{position:absolute;top:0;left:50%;transform:translateX(-50%);width:18px;height:18px;border-radius:50%;border:2px solid var(--ink-3);background:var(--paper);display:grid;place-items:center;font-size:10px;z-index:1}
.stepper li.done .dot{background:var(--ink);border-color:var(--ink);color:var(--paper)}
.stepper li.cur{color:var(--ink);font-weight:600}
.stepper li.cur .dot{border:4px solid var(--ink)}
.journey{display:flex;gap:18px;align-items:center;flex-wrap:wrap}
.journey .acts{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
/* readiness */
.ready{border:1px solid var(--line);border-radius:8px;padding:14px;background:var(--surface)}
.ready ul{list-style:none;margin:10px 0 0;padding:0}
.ready li{display:grid;grid-template-columns:22px 1fr;gap:6px;padding:6px 0;border-top:1px solid var(--line-2)}
.ready li:first-child{border-top:0}
.ready .mk{width:18px;height:18px;border-radius:50%;border:1.5px solid var(--ink-3);display:grid;place-items:center;font-size:10px;margin-top:2px}
.ready li.met .mk{background:var(--t-good-fg);border-color:var(--t-good-fg);color:var(--surface)}
.ready li.met .g{text-decoration:line-through;text-decoration-thickness:1px;color:var(--ink-2)}
.ready li .why{display:block;color:var(--ink-2);font-size:12.5px}
.ready li .fix{font-size:12.5px}
.rule{border-left:3px solid var(--accent);background:var(--accent-soft);padding:10px 12px;border-radius:0 6px 6px 0;font-size:13px;color:var(--ink)}
.rule b{font-weight:600}
/* lineage */
.lineage{display:flex;flex-wrap:wrap;gap:6px;align-items:center;font-size:12.5px}
.lineage .n{border:1px solid var(--line);border-radius:6px;padding:4px 8px;background:var(--surface);display:inline-flex;gap:6px;align-items:center}
.lineage .n.cur{background:var(--ink);color:var(--paper);border-color:var(--ink)}
.lineage .n em{font-style:normal;color:var(--ink-2)}
.lineage .n.cur em{color:var(--paper);opacity:.85}
.lineage .sep{color:var(--ink-2)}
/* kanban */
.kan{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
.col h4{margin:0 0 8px;font:500 11px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2);display:flex;justify-content:space-between}
.kc{background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:10px;margin-bottom:8px}
.kc .p{font-size:11.5px;color:var(--ink-2)}
.kc .t{font-weight:500;margin:2px 0 8px;line-height:1.3}
/* sheet */
.sheet{width:100%;border-collapse:collapse;font-size:13px;background:var(--surface);border:1px solid var(--line);border-radius:8px;overflow:hidden}
.sheet th{font:500 10.5px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2);text-align:left;padding:8px 10px;border-bottom:1px solid var(--line);background:var(--paper)}
.sheet td{padding:8px 10px;border-bottom:1px solid var(--line-2);vertical-align:top}
.sheet td .h{display:block;color:var(--ink-2);font-size:11.5px;margin-top:2px}
.sheet td.sel{outline:2px solid var(--focus);outline-offset:-2px}
.sheet tr.refused td{background:var(--t-alert-bg)}
.cell-date{display:inline-flex;gap:6px;align-items:center;border:1px solid var(--line);border-radius:6px;padding:3px 8px;background:var(--paper)}
.kbd{font:500 11px var(--mono);border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;padding:0 5px;background:var(--surface)}
.saving{font-size:12px;color:var(--ink-2)}
/* timeline */
.tl{list-style:none;margin:0;padding:0}
.tl .day{font:500 11px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2);padding:12px 0 4px}
.tl li.e{display:grid;grid-template-columns:44px auto 1fr;gap:10px;align-items:baseline;padding:6px 0;border-top:1px solid var(--line-2)}
.tl .tm{font:400 12px var(--mono);color:var(--ink-2)}
.tl .s{font:400 15px/1.45 var(--text-serif)}
@media (max-width:560px){.tl li.e{grid-template-columns:44px 1fr}.tl li.e .s{grid-column:1/-1}}
/* empty / no access */
.state{border:1px solid var(--line);border-radius:8px;background:var(--surface);padding:22px;text-align:center}
.state b{display:block;font:400 22px var(--display);margin-bottom:4px}
.state p{margin:0 auto;color:var(--ink-2);font-size:13px;max-width:44ch}
.banner{display:flex;gap:12px;flex-wrap:wrap;align-items:baseline;border:1px solid var(--t-alert-fg);background:var(--t-alert-bg);color:var(--t-alert-fg);padding:12px 14px;border-radius:6px;font-size:13.5px}
.banner .label{color:inherit;font-weight:600}
.banner span{color:var(--ink)}
/* side panel / drawer */
.drawer{background:var(--surface);border-left:1px solid var(--line);padding:16px;box-shadow:-12px 0 30px -18px rgba(0,0,0,.25)}
.field{margin:12px 0}
.field label{display:block;font-weight:500;font-size:13px;margin-bottom:4px}
.field .in{border:1px solid var(--ink-3);border-radius:6px;min-height:38px;padding:8px 10px;background:var(--paper);color:var(--ink)}
.field .in.ta{min-height:64px}
.field .hint{display:block;color:var(--ink-2);font-size:12px;margin-top:4px}
.field .err{display:block;color:var(--t-alert-fg);font-size:12px;margin-top:4px;font-weight:500}
/* picker popovers */
.pop{background:var(--surface);border:1px solid var(--line);border-radius:10px;box-shadow:0 14px 34px -16px rgba(0,0,0,.35);padding:10px;font-size:13px}
.pop .opt{display:flex;gap:10px;align-items:center;padding:6px 8px;border-radius:6px;min-height:40px}
.pop .opt.on{background:var(--wire)}
.cal{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;text-align:center;font-size:12px}
.cal span{padding:5px 0;border-radius:6px}
.cal .hd{color:var(--ink-2);font-size:10.5px;letter-spacing:.06em}
.cal .off{color:var(--ink-3);opacity:.6}
.cal .on{background:var(--ink);color:var(--paper)}
.cal .today{box-shadow:inset 0 0 0 1px var(--ink)}
.cal .out{text-decoration:line-through;color:var(--ink-3)}
/* pattern cards */
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,350px),1fr));gap:28px 28px}
.card{display:flex;flex-direction:column;gap:12px;min-width:0}
.card h3{font-size:24px}
.card .kit{font:400 12px var(--mono);color:var(--ink-2)}
.card dl{margin:0;font-size:13.5px;display:grid;grid-template-columns:auto 1fr;gap:4px 12px}
.card dt{font:500 10.5px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2);padding-top:3px}
.card dd{margin:0}
/* screens */
.screen{padding:40px 0;border-top:1px solid var(--line)}
.screen:first-of-type{border-top:0}
.screen .why{color:var(--ink-2);margin:6px 0 20px;max-width:75ch}
.pair{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:28px;align-items:start}
@media (max-width:1000px){.pair{grid-template-columns:minmax(0,1fr)}}
.frame-cap{font:500 11px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2);margin-bottom:8px;display:flex;gap:8px;align-items:center}
.desk{border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--paper);min-width:0}
.desk .bar{display:flex;gap:16px;align-items:center;padding:10px 16px;border-bottom:1px solid var(--line);font-size:12.5px;color:var(--ink-2);flex-wrap:wrap}
.desk .bar b{color:var(--ink);font-weight:600;letter-spacing:.08em;font-size:11px;text-transform:uppercase}
.desk .bar .on{color:var(--ink);border-bottom:2px solid var(--accent);padding-bottom:2px}
.desk .body{padding:20px}
.phone{width:100%;max-width:340px;border:1px solid var(--line);border-radius:28px;padding:14px 12px 18px;background:var(--paper);margin:0 auto}
.phone .notch{width:80px;height:5px;border-radius:9px;background:var(--wire-2);margin:0 auto 12px}
.phone .wf{border:0;padding:4px;background:transparent}
.split{display:grid;grid-template-columns:minmax(0,1fr) 270px;gap:18px}
@media (max-width:820px){.split{grid-template-columns:minmax(0,1fr)}}
@media (max-width:820px){.kan{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:480px){.kan{grid-template-columns:minmax(0,1fr)}}
/* phone list rows (tables reflow into these) */
.prow{border-top:1px solid var(--line);padding:10px 0;display:grid;grid-template-columns:auto 1fr;gap:4px 10px;align-items:start}
.prow .t{font-weight:500}
.prow .meta{grid-column:2;display:flex;gap:6px;flex-wrap:wrap;align-items:center}
/* component tables */
.ctab-wrap{margin-top:24px}
table.ctab{width:100%;border-collapse:collapse;font-size:13.5px}
.ctab th{font:500 10.5px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2);text-align:left;padding:8px 10px 8px 0;border-bottom:1px solid var(--ink)}
.ctab td{padding:10px 10px 10px 0;border-bottom:1px solid var(--line);vertical-align:top}
.ctab td:first-child{font:500 12.5px var(--mono);white-space:nowrap}
.ctab .r{display:block;margin:2px 0}
.ctab .r em{font-style:normal;font:600 10px var(--sans);letter-spacing:.08em;text-transform:uppercase;padding:1px 6px;border-radius:4px;margin-right:6px}
.r-v em{background:var(--t-good-bg);color:var(--t-good-fg)}
.r-b em{background:var(--t-wait-bg);color:var(--t-wait-fg)}
.r-h em{background:var(--t-muted-bg);color:var(--t-muted-fg)}
@media (max-width:760px){
table.ctab,.ctab tbody,.ctab tr,.ctab td{display:block;width:100%}
.ctab thead{position:absolute;left:-9999px}
.ctab tr{border-top:1px solid var(--ink);padding:8px 0}
.ctab td{border:0;padding:4px 0}
.ctab td::before{content:attr(data-h);display:block;font:500 10px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2)}
.ctab td:first-child{white-space:normal}
}
.legend{display:flex;flex-wrap:wrap;gap:16px;font-size:13px;margin-top:14px}
.qs{counter-reset:q;list-style:none;padding:0;margin:0}
.qs li{counter-increment:q;display:grid;grid-template-columns:44px 1fr;gap:8px;padding:18px 0;border-top:1px solid var(--line)}
.qs li::before{content:counter(q,decimal-leading-zero);font:400 28px/1 var(--display);color:var(--accent)}
.qs b{display:block;font:400 21px/1.25 var(--display);margin-bottom:4px}
.qs p{margin:0;color:var(--ink-2)}
.fixlist{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr));gap:0 28px;margin-top:20px}
.fixlist div{border-top:1px solid var(--line);padding:12px 0;font-size:14px}
.fixlist s{color:var(--ink-2)}
footer{padding:32px 0 56px;color:var(--ink-2);font-size:13px}
@media (prefers-reduced-motion:no-preference){.btn,.toggle{transition:background-color .15s,color .15s}}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.sid{font:500 12px var(--mono);letter-spacing:0;text-transform:none;background:var(--ink);color:var(--paper);padding:2px 7px;border-radius:4px}
.sign{border:1px solid var(--ink);border-radius:8px;padding:14px 16px;margin:0 0 24px;background:var(--surface)}
.sign dl{display:grid;grid-template-columns:130px 1fr;gap:6px 16px;margin:10px 0 0;font-size:13.5px}
.sign dt{font:500 10.5px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2);padding-top:3px}
.sign dd{margin:0}.sign ul{margin:0;padding-left:18px}.sign li{margin:2px 0}
@media (max-width:560px){.sign dl{grid-template-columns:1fr}}
@media (max-width:700px){.desk .sheet{display:block;overflow-x:auto}.desk .body{padding:14px}}
.tosign{border:2px solid var(--ink);border-radius:10px;padding:20px;background:var(--surface)}
.tosign ul{list-style:none;padding:0;margin:12px 0}.tosign li{display:flex;gap:12px;align-items:center;flex-wrap:wrap;padding:10px 0;border-top:1px solid var(--line)}
.tosign .box2{width:22px;height:22px;border:1.5px solid var(--ink);border-radius:4px;flex:none}
</style>
</head>
<body>
<header class="top"><div class="wrap">
<span class="brand">erp-ui · design contract · draft for approval</span>
<button class="toggle" id="theme" type="button" aria-label="Switch colour theme">Dark / light</button>
</div></header>
<main>
<!-- ============ INTRO ============ -->
<section class="block"><div class="wrap">
<div class="label">erp-ui · shared screen patterns · applied to Projects</div>
<h1 style="margin-top:10px">The screen is the process,<br>told calmly.</h1>
<p class="lede">Eight shared patterns every brick's ModuleUI builds from, and the Projects brick drawn with them. Wireframes only: grey boxes, real words, invented data. Nothing here is built until you approve it.</p>
<div class="rules">
<div><span class="label">Rule 1</span><b>One primary action per screen</b><span>The next step of the signed process. Everything else is secondary, or a quiet link for an exit.</span></div>
<div><span class="label">Rule 2</span><b>State is shown, not stacked</b><span>Pills, a stepper, Readiness, figures and linked documents. Very few cards; thin rules instead of card headers.</span></div>
<div><span class="label">Rule 3</span><b>The server decides, the screen asks</b><span>Nothing shows as saved until the server confirms. A refusal keeps the old value and shows the server's sentence.</span></div>
<div><span class="label">Rule 4</span><b>Components never fetch</b><span>Props in, events out, so the gallery runs on made-up data. The ModuleUI wires the generated OpenAPI client.</span></div>
</div>
<h3 style="margin-top:36px">Who sees an action</h3>
<div class="fixlist">
<div><span class="pill t-muted"><i aria-hidden="true">–</i>Hidden</span><br>The role can <b>never</b> take it (no permission). A section with no access is left out of the nav; opened by URL it shows No access.</div>
<div><span class="pill t-wait"><i aria-hidden="true">◷</i>Shown, blocked</span><br>The role can take it, but the <b>state or a rule</b> stops it now. The button stays, and Readiness beside it says what's missing. Never greyed out without its reason next to it.</div>
<div><span class="pill t-good"><i aria-hidden="true">✓</i>Shown, ready</span><br>Permission held and every gate met. The server is still the real gate: a 403 or 409 comes back as one sentence.</div>
</div>
<h3 style="margin-top:36px">Tones carry meaning, not colour</h3>
<p class="muted" style="margin:6px 0 0">Derived from the workflow's flags, never from a state's name, so a renamed state keeps its tone; the colours themselves are Look, below. Every pill has a glyph and words, so no tone relies on colour alone. All pairs pass WCAG AA in both themes.</p>
<div class="tones">
<span class="pill"><i aria-hidden="true">●</i>neutral · planned</span>
<span class="pill t-info"><i aria-hidden="true">▸</i>info · in progress</span>
<span class="pill t-warm"><i aria-hidden="true">◆</i>warm · needs you</span>
<span class="pill t-good"><i aria-hidden="true">✓</i>good · accepted</span>
<span class="pill t-muted"><i aria-hidden="true">–</i>muted · descoped</span>
<span class="pill t-wait"><i aria-hidden="true">◷</i>wait · with the client</span>
<span class="pill t-alert"><i aria-hidden="true">!</i>alert · at risk</span>
</div>
<h3 style="margin-top:36px">Fixed against the reference and the current CRM pages</h3>
<div class="fixlist">
<div><s>Grey-on-beige secondary text and faint text links</s><br>Secondary text is #4A453E on paper (8.6:1); links are underlined, never colour-only.</div>
<div><s>Native date inputs showing 17/10/2026 and 08/11/2026</s><br>A labelled DatePicker with one format everywhere: <b>14 Nov 2026</b>, in your time zone, never UTC.</div>
<div><s>Plan as a table of bare inputs</s><br>A Sheet with labels, a hint under every column, names prefilled from the quote line and people chosen as avatars.</div>
<div><s>Every button shown, refused after the click</s><br>Hidden when the role can never act; shown with its Readiness reason when only state blocks it.</div>
<div><s>"Overdue" on an item that is with the client</s><br>"With the client until 16 Nov": the review clock, in the wait tone. Client time is not our slippage.</div>
<div><s>"plan from contract — contract_6430…, line ql_impl"</s><br>"Planned from the signed contract <u>Spring launch agreement</u>, line 2: Video production · 60s."</div>
<div><s>"missing permission: pj:project:read"</s><br>"You don't have access to Projects. Ask Mira Holm, your team admin." The permission name sits behind a disclosure for the admin.</div>
<div><s>A timeline 1641px wide at 1400; a phone home 492px wide at 390</s><br>Nothing scrolls sideways. Tables reflow into rows on a phone; touch targets are 44px.</div>
</div>
</div></section>
<!-- ============ A) PATTERNS ============ -->
<section class="block" id="patterns"><div class="wrap">
<div class="sec-head"><span class="n">A</span><h2>Patterns</h2></div>
<p class="intro">Each card shows the wireframe, the kit pieces it is made of, and the states every instance must draw. Kit names match the reference kit where they mean the same thing.</p>
<div class="cards">
<!-- ListOrBoard -->
<article class="card">
<h3>ListOrBoard</h3>
<div class="kit">Kanban · Sheet · SubTabs · Toolbar · Search · Select · Pill · Avatar</div>
<div class="wf">
<div class="row between"><div class="seg"><span class="on">Mine</span><span>All</span></div><div class="tabs" style="border:0"><span class="on">Board</span><span>List</span></div></div>
<div class="row" style="margin:10px 0"><div class="box" style="height:30px;flex:1"></div><div class="box" style="height:30px;width:90px"></div></div>
<div class="kan" style="grid-template-columns:repeat(3,minmax(0,1fr))">
<div class="col"><h4>Planned <span>3</span></h4><div class="kc"><div class="p">Harbor & Pine</div><div class="t">Launch photography</div><div class="row between"><span class="pill">● 20 Nov</span><span class="av">AL</span></div></div></div>
<div class="col"><h4>Working <span>2</span></h4><div class="kc"><div class="p">Northwind</div><div class="t">Brand guide v2</div><div class="row between"><span class="pill t-info">▸ 12 Nov</span><span class="av">SO</span></div></div></div>
<div class="col"><h4>Review <span>1</span></h4><div class="kc"><div class="p">Harbor & Pine</div><div class="t">Hero film — 60s</div><span class="pill t-wait">◷ with client to 16 Nov</span></div></div>
</div>
</div>
<dl>
<dt>Shows</dt><dd>Records grouped by state (Board) or sorted by urgency (List). Columns come from the process; names are the customer's rows.</dd>
<dt>States</dt><dd>loading (column skeletons) · empty (EmptyState, per column a dash) · error (Note + Retry) · filtered-to-nothing ("No items match; clear filters") · no access.</dd>
<dt>Phone</dt><dd>Always the List, grouped by urgency; a Board column becomes a SubTab.</dd>
</dl>
</article>
<!-- RecordJourney -->
<article class="card">
<h3>RecordJourney</h3>
<div class="kit">PageTitle · Stepper · Button · StageRail · Lineage</div>
<div class="wf">
<div class="kick">Item · Harbor & Pine</div>
<div class="ttl">Hero film — 60s</div>
<hr>
<div class="journey">
<ol class="stepper">
<li class="done"><span class="dot" aria-hidden="true">✓</span>Planned</li>
<li class="done"><span class="dot" aria-hidden="true">✓</span>Working</li>
<li class="cur" aria-current="step"><span class="dot"></span>Review</li>
<li><span class="dot"></span>Done</li>
</ol>
<div class="acts"><span class="btn primary">Record acceptance</span><span class="btn">Request changes…</span></div>
</div>
<div class="row" style="justify-content:flex-end;margin-top:8px"><span class="qlink">Descope…</span></div>
</div>
<dl>
<dt>Shows</dt><dd>The record's place in the signed process and the one next step beside it. Side branches (blocked) show as a pill on the current step, not a new step.</dd>
<dt>Actions</dt><dd>1 primary (the process's next transition) · ≤ 2 secondary · exits as quiet links. Each is hidden, blocked or ready per the rules above.</dd>
<dt>States</dt><dd>loading · terminal (stepper complete, primary gone, "Accepted 18 Nov by Mira Holm") · stale (server version moved: "Someone changed this. Reload").</dd>
</dl>
</article>
<!-- Readiness / GateChecklist -->
<article class="card">
<h3>Readiness <span class="muted" style="font-size:16px">(GateChecklist)</span></h3>
<div class="kit">Readiness · Note (rule) · Pill</div>
<div class="wf">
<div class="rule"><b>Acceptance rule:</b> an item is done when the client accepts it with evidence attached. Whoever submitted it doesn't record it.</div>
<div class="ready" style="margin-top:10px">
<div class="row between"><span class="label">Ready to record acceptance</span><span class="pill t-warm">◆ 1 to go</span></div>
<ul>
<li class="met"><span class="mk" aria-hidden="true">✓</span><span><span class="g">Submitted for review</span><span class="sr"> (met)</span></span></li>
<li class="met"><span class="mk" aria-hidden="true">✓</span><span><span class="g">Evidence attached</span><span class="sr"> (met)</span></span></li>
<li><span class="mk" aria-hidden="true"></span><span>Recorded by someone else<span class="sr"> (not met)</span><span class="why">You submitted this, so Mira Holm or another lead records it.</span><a class="fix" href="#review">Ask Mira →</a></span></li>
</ul>
</div>
</div>
<dl>
<dt>Fed by</dt><dd>The gates of the transition in the brick's <span class="mono">process.yaml</span> (the customer's signed BPMN), and the brick's own evaluation of each: met, not met + reason + fix link. erp-ui never evaluates a gate itself.</dd>
<dt>Shows</dt><dd>One line per gate, struck through and ticked when met (strike + tick + "met" for screen readers, not colour). The rule in plain words above it.</dd>
<dt>States</dt><dd>all met (collapses to "Ready" + the primary) · some unmet (count pill) · evaluating · gate switched off by the customer (line omitted, never shown struck).</dd>
</dl>
</article>
<!-- FiguresRow -->
<article class="card">
<h3>FiguresRow</h3>
<div class="kit">Figure × 3–4</div>
<div class="wf">
<div class="figs">
<div><span class="label">Delivered</span><b>3 of 8</b><small>accepted items</small></div>
<div><span class="label">With client</span><b>2</b><small>in review</small></div>
<div><span class="label">At risk</span><b>1</b><small>window ends 30 Nov</small></div>
<div><span class="label">Invoiced</span><b>2 of 3</b><small>billable on acceptance</small></div>
</div>
</div>
<dl>
<dt>Shows</dt><dd>3–4 numbers, each a big serif figure with one line that says what it means. A figure that links filters the list below.</dd>
<dt>States</dt><dd>loading (number skeleton, the meaning line already shown) · zero ("0", never blank) · unknown ("—" + "invoicing not connected") · alert (figure gets the tone pill, not red digits).</dd>
</dl>
</article>
<!-- SheetEditor -->
<article class="card">
<h3>SheetEditor</h3>
<div class="kit">Sheet · EditField · Picker · DateInput · SaveBar · useCommit</div>
<div class="wf" style="padding:12px">
<table class="sheet">
<thead><tr><th>Item</th><th>Owner</th><th>Due</th></tr></thead>
<tbody>
<tr><td>Hero film — 60s<span class="h">from line 2</span></td><td><span class="av">AL</span></td><td class="sel"><span class="cell-date">14 Nov 2026</span></td></tr>
<tr class="refused"><td>Social cut-down 1 of 6</td><td><span class="av">SO</span></td><td><span class="cell-date">28 Nov 2026</span><span class="h" style="color:var(--t-alert-fg)">2 Dec is after the window (30 Nov). Kept 28 Nov.</span></td></tr>
</tbody>
</table>
<p class="small muted" style="margin:8px 0 0"><span class="kbd">↑↓←→</span> move · <span class="kbd">Enter</span> edits · <span class="kbd">Tab</span> commits · <span class="kbd">Esc</span> cancels · a refused edit restores the old value and says why.</p>
</div>
<dl>
<dt>Shows</dt><dd>Line items edited in place. Header label + one-line hint per column. People are avatars, dates a DatePicker, links a Picker; nothing typed twice.</dd>
<dt>Cell states</dt><dd>confirmed · editing · saving ("Saving…") · saved (fades after 1.5s) · refused (old value back + server's sentence) · read-only (no hover affordance, lock in the header with the reason).</dd>
<dt>Phone</dt><dd>Reflows to one row per item; tapping opens the row in a Drawer with labelled Fields.</dd>
</dl>
</article>
<!-- SidePanelForm -->
<article class="card">
<h3>SidePanelForm</h3>
<div class="kit">Drawer · Field · Dialog (create) · SaveBar</div>
<div class="wf" style="display:grid;grid-template-columns:1fr 62%;gap:0;padding:0;overflow:hidden">
<div style="padding:12px"><div class="box" style="height:14px;width:70%"></div><div class="box" style="height:60px;margin-top:10px"></div><div class="box" style="height:40px;margin-top:10px"></div></div>
<div class="drawer">
<div class="label">Request changes</div>
<div class="field"><label for="x1">What should change</label><div class="in ta" id="x1">Shorter end card, logo on the left</div><span class="hint">The client's words. The same item goes back to work as revision 2.</span></div>
<div class="row"><span class="btn primary">Send back to work</span><span class="qlink">Cancel</span></div>
</div>
</div>
<dl>
<dt>Shows</dt><dd>A transition that needs input (a reason, evidence) opens beside the record, not over it. Sections, a label and a hint under every field, prefilled from upstream. Creating a record is a small Dialog, then you land on the new record.</dd>
<dt>States</dt><dd>pristine · invalid (field-level sentence, focus moves to it) · submitting · refused (the server's sentence at the top, input kept) · done (panel closes, the timeline gains a line).</dd>
</dl>
</article>
<!-- Timeline -->
<article class="card">
<h3>Timeline</h3>
<div class="kit">Timeline · Pill · Link</div>
<div class="wf">
<ul class="tl">
<li class="day">Mon 2 Nov 2026</li>
<li class="e"><span class="tm">14:20</span><span class="pill t-wait">◷ Review</span><span class="s">Jonas Berg submitted it for review with a link to <a href="#history">Final cut v3</a>.</span></li>
<li class="day">Wed 14 Oct 2026</li>
<li class="e"><span class="tm">09:02</span><span class="pill">● Plan</span><span class="s">Planned from the signed contract <a href="#history">Spring launch agreement</a>, line 2.</span></li>
</ul>
</div>
<dl>
<dt>Shows</dt><dd>The audit log and workflow events as sentences, grouped by day, in your time zone: time, a typed chip, a sentence with people's names and linked records. Ids never appear.</dd>
<dt>States</dt><dd>loading · empty ("Nothing has happened yet. Starting, blocking and submitting show here.") · partial ("Some events are from a section you can't see.") · error.</dd>
</dl>
</article>
<!-- Empty / Error / NoAccess -->
<article class="card">
<h3>Empty · Error · NoAccess</h3>
<div class="kit">EmptyState · Note · NoAccess</div>
<div class="wf" style="display:grid;gap:10px">
<div class="state"><b>No items are due this week</b><p>Items you own appear here when they are due within 7 days. Items with the client are under Review.</p></div>
<div class="banner" role="alert"><span class="label">Couldn't load</span><span>The projects service didn't answer. Your changes are safe. <a href="#patterns">Try again</a></span></div>
<div class="banner"><span class="label">No access</span><span>Your role does not include Projects. Mira Holm, your team admin, can add it on the People page.</span></div>
</div>
<dl>
<dt>Shows</dt><dd>Empty explains what would appear and why. Error says what failed, that nothing was lost, and offers one retry. NoAccess names the section in words and the person who can grant it.</dd>
<dt>Rule</dt><dd>A section the user can't read is left out of the nav; NoAccess only appears when opened by URL. A record outside one's scope answers like a dead link (PJ-05), never "forbidden".</dd>
</dl>
</article>
</div>
</div></section>
<!-- ============ B) SCREENS ============ -->
<section class="block" id="screens"><div class="wrap">
<div class="sec-head"><span class="n">B</span><h2>Projects, drawn with the patterns</h2></div>
<p class="intro">Invented data: the client Harbor & Pine bought a spring launch (a 60-second film, six social cut-downs, a photo day). Today is Mon 9 Nov 2026. Review window 10 working days, warning notice 5 (the brick's defaults). People: Jonas Berg (project lead), Ana Lind and Sam Okafor (team), Mira Holm (operations manager and team admin).</p>
<!-- B1 BOARD -->
<div class="screen" id="board">
<div class="label"><span class="sid">projects.board</span> · stories PJ-02, PJ-08, PJ-11</div>
<h3>Board</h3>
<p class="why">What's due across every client, my own work first. An item in review is <b>with the client until a date</b>, never overdue: client time is not our slippage.</p>
<div class="sign"><div class="row between"><span class="label">Signed parts</span><span class="pill t-info">▸ part of the signature</span></div>
<dl><dt>Purpose</dt><dd>What is due across every client, my own work first; client time shown as a clock, never as overdue.</dd><dt>Data shown</dt><dd>Items I may read: client, project, title, owner, state, due date; in review the review clock (window end, warning due, deemed earliest); blocked: reason and days.</dd><dt>Actions → BPMN</dt><dd><ul><li><span class="mono">start</span> drag Planned → In progress, own items, pj:item:work · hidden otherwise</li><li><span class="mono">block</span> drag to Blocked → reason panel, pj:item:work · reason required</li><li><span class="mono">unblock</span> drag Blocked → In progress, pj:item:work</li><li><span class="mono">submit</span> drag to In review → evidence panel, pj:item:work · blocked without evidence</li><li><span class="mono">ai_chase_brief</span> "Review it" opens the drafted reminder; a person sends it · hidden when the step is off</li><li><span class="mono">—</span> Mine / All: All hidden without pj:project:read_all or pj:project:manage</li></ul></dd><dt>States</dt><dd>loading · empty · filtered to nothing · error · no access (→ projects.no_access)</dd></dl></div>
<div class="pair">
<div>
<div class="frame-cap">Desktop</div>
<div class="desk">
<div class="bar"><b>Deliver</b><span class="on">Board</span><span>Projects</span><span>Reconciliation</span></div>
<div class="body">
<div class="row between">
<div><div class="label">Deliver</div><div style="font:400 40px/1 var(--display);margin-top:4px">Board</div><div class="muted small" style="margin-top:6px">Every item you deliver, across every client. Times in your zone (Europe/Stockholm).</div></div>
<div class="seg" role="tablist" aria-label="Whose items"><span class="on">Mine</span><span>All</span></div>
</div>
<div class="figs" style="margin-top:18px">
<div><span class="label">Due this week</span><b>5</b><small>2 of them today</small></div>
<div><span class="label">With the client</span><b>3</b><small>review clock running</small></div>
<div><span class="label">Blocked</span><b>2</b><small>waiting on a brief</small></div>
<div><span class="label">Warning due</span><b>1</b><small>a person sends it</small></div>
</div>
<div class="row" style="margin:16px 0"><div class="box" style="height:36px;flex:1 1 200px"></div><span class="btn">Any client ▾</span><span class="btn">Any project ▾</span></div>
<div class="kan">
<div class="col"><h4>Planned <span>2</span></h4>
<div class="kc"><div class="p">Harbor & Pine · Spring launch</div><div class="t">Launch photography — 1 day</div><div class="row between"><span class="pill">● due 20 Nov</span><span class="av">AL</span></div></div>
<div class="kc"><div class="p">Northwind · Rebrand</div><div class="t">Brand guide — print edition</div><div class="row between"><span class="pill">● due 27 Nov</span><span class="av">AL</span></div></div>
</div>
<div class="col"><h4>In progress <span>2</span></h4>
<div class="kc"><div class="p">Harbor & Pine · Spring launch</div><div class="t">Social cut-down 1 of 6 — Instagram 9:16</div><div class="row between"><span class="pill t-alert">! due today</span><span class="av">AL</span></div></div>
<div class="kc"><div class="p">Northwind · Rebrand</div><div class="t">Brand guide — web edition</div><div class="row between"><span class="pill t-info">▸ due 12 Nov</span><span class="av">AL</span></div></div>
</div>
<div class="col"><h4>Blocked <span>1</span></h4>
<div class="kc"><div class="p">Harbor & Pine · Spring launch</div><div class="t">Social cut-down 2 of 6 — Stories</div><span class="pill t-wait">◷ waiting on the client's assets · 4 days</span><div class="small muted" style="margin-top:6px">Reminder drafted · <a href="#board">Review it</a></div></div>
</div>
<div class="col"><h4>In review <span>2</span></h4>
<div class="kc"><div class="p">Harbor & Pine · Spring launch</div><div class="t">Hero film — 60s</div><span class="pill t-wait">◷ with the client until 16 Nov</span><div class="small muted" style="margin-top:6px">5 working days left</div></div>
<div class="kc"><div class="p">Northwind · Rebrand</div><div class="t">Logo suite — final files</div><span class="pill t-warm">◆ warning due today</span><div class="small muted" style="margin-top:6px">No answer in 10 working days</div></div>
</div>
</div>
</div>
</div>
</div>
<div>
<div class="frame-cap">Phone</div>
<div class="phone touch"><div class="notch"></div><div class="wf">
<div class="kick">Deliver</div><div class="ttl">Board</div>
<div class="row between" style="margin-top:10px"><div class="seg"><span class="on" style="padding:10px 16px">Mine</span><span style="padding:10px 16px">All</span></div><span class="btn">Filter</span></div>
<div class="figs" style="margin-top:12px"><div><span class="label">Due</span><b>5</b><small>this week</small></div><div><span class="label">With client</span><b>3</b><small>review clock</small></div></div>
<div class="label" style="margin-top:14px">Today</div>
<div class="prow"><span class="av lg">AL</span><div><div class="t">Social cut-down 1 of 6</div><div class="small muted">Harbor & Pine</div></div><div class="meta"><span class="pill t-alert">! due today</span></div></div>
<div class="prow"><span class="av lg">JB</span><div><div class="t">Logo suite — final files</div><div class="small muted">Northwind</div></div><div class="meta"><span class="pill t-warm">◆ warning due</span></div></div>
<div class="label" style="margin-top:12px">This week</div>
<div class="prow"><span class="av lg">AL</span><div><div class="t">Brand guide — web</div><div class="small muted">Northwind</div></div><div class="meta"><span class="pill t-info">▸ due 12 Nov</span></div></div>
<div class="label" style="margin-top:12px">Waiting on others</div>
<div class="prow"><span class="av lg">JB</span><div><div class="t">Hero film — 60s</div><div class="small muted">Harbor & Pine</div></div><div class="meta"><span class="pill t-wait">◷ with client to 16 Nov</span></div></div>
</div></div>
</div>
</div>
<div class="ctab-wrap"><table class="ctab">
<thead><tr><th>Component</th><th>Shows</th><th>States</th><th>Action rules</th></tr></thead>
<tbody>
<tr><td data-h="Component">SubTabs Mine / All</td><td data-h="Shows">My items, or every project I can read</td><td data-h="States">Mine is the default</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>All: with pj:project:read_all or pj:project:manage</span><span class="r r-h"><em>hidden</em>All: without them (the toggle disappears, Mine is the page)</span></td></tr>
<tr><td data-h="Component">FiguresRow</td><td data-h="Shows">Due this week · With the client · Blocked · Warning due</td><td data-h="States">loading · zero shows "0"</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Each figure filters the board</span></td></tr>
<tr><td data-h="Component">Kanban (ListOrBoard)</td><td data-h="Shows">Columns = item states (customer's names from pj_state); cards with client, title, owner, due pill. Phone: List by urgency (PJ-08: overdue, today, this week)</td><td data-h="States">loading · empty column "—" · no items ("Nothing is due. Planned items appear when a contract is signed.") · error · no access</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Drag to In progress / Review: own items with pj:item:work; drop runs the transition and may open its SidePanelForm</span><span class="r r-b"><em>blocked</em>Drop refused by a gate: card snaps back, the gate's sentence shows</span><span class="r r-h"><em>hidden</em>Dragging for viewer / billing (cards are links only)</span></td></tr>
<tr><td data-h="Component">Pill (due / clock)</td><td data-h="Shows">Due date; in review: "with the client until <window end>", then "warning due", then "deemed possible from <date>" (PJ-11)</td><td data-h="States">Tone from flags: in review = wait, warning due = warm, past due & ours = alert, blocked on client = wait</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Always; read-only</span></td></tr>
</tbody></table></div>
</div>
<!-- B2 PROJECT -->
<div class="screen" id="project">
<div class="label"><span class="sid">projects.project</span> · stories PJ-01, PJ-03, PJ-13</div>
<h3>Project page</h3>
<p class="why">One page tells the whole journey: where the money came from, what is delivered, what is with the client, what is at risk, what is invoiced, and the next thing to do.</p>
<div class="sign"><div class="row between"><span class="label">Signed parts</span><span class="pill t-info">▸ part of the signature</span></div>
<dl><dt>Purpose</dt><dd>One project's journey from the sale to reconciliation, and the next thing to do.</dd><dt>Data shown</dt><dd>Lineage (quote, contract, project, invoices with states); delivered x of n, with the client, at risk, invoiced; items with owner, state and clock; reconcile readiness.</dd><dt>Actions → BPMN</dt><dd><ul><li><span class="mono">plan_project</span> shown in the lineage (system) · no button</li><li><span class="mono">activate</span> "Start the project", pj:project:write · blocked until the first item started</li><li><span class="mono">ai_draft_plan</span> "Review the suggestions" → projects.plan, pj:project:plan · hidden when the step is off</li><li><span class="mono">ai_flag_shortfall</span> the At risk figure and readiness line (system) · no button</li><li><span class="mono">reconcile</span> none: system only (pj:project:reconcile is machine-only)</li><li><span class="mono">raise_makegood</span> pj:project:write · blocked until reconciled short · hidden otherwise</li><li><span class="mono">cancel_project</span> quiet link, reason required, pj:project:write or manage · hidden otherwise</li></ul></dd><dt>States</dt><dd>loading · planned · active · closed (reconciliation shown) · cancelled · error · no access</dd></dl></div>
<div class="pair">
<div>
<div class="frame-cap">Desktop · as Jonas, the lead</div>
<div class="desk">
<div class="bar"><b>Deliver</b><span>Board</span><span class="on">Projects</span><span>Reconciliation</span></div>
<div class="body">
<div class="row between"><div><div class="label">Project · Harbor & Pine</div><div style="font:400 40px/1.05 var(--display);margin-top:4px">Spring launch</div></div><div class="row"><span class="btn">Client link</span></div></div>
<div class="lineage" style="margin-top:14px" aria-label="Linked documents">
<span class="n">Quote Q-0412 <em>accepted</em></span><span class="sep" aria-hidden="true">›</span>
<span class="n">Spring launch agreement <em>signed 14 Oct</em></span><span class="sep" aria-hidden="true">›</span>
<span class="n cur" aria-current="page">Project <em>active</em></span><span class="sep" aria-hidden="true">›</span>
<span class="n">Invoices <em>2 drafted</em></span>
</div>
<div class="figs" style="margin-top:18px">
<div><span class="label">Delivered</span><b>3 of 8</b><small>accepted, with evidence</small></div>
<div><span class="label">With the client</span><b>2</b><small>review clock running</small></div>
<div><span class="label">At risk</span><b>1</b><small>window ends 30 Nov</small></div>
<div><span class="label">Invoiced</span><b>2 of 3</b><small>lines billed on acceptance</small></div>
</div>
<div class="journey" style="margin-top:20px">
<ol class="stepper">
<li class="done"><span class="dot" aria-hidden="true">✓</span>Plan</li>
<li class="cur" aria-current="step"><span class="dot"></span>Work</li>
<li><span class="dot"></span>Review</li>
<li><span class="dot"></span>Reconcile<br><span class="small muted">30 Nov</span></li>
</ol>
<div class="acts"><span class="btn primary">Plan the photography day</span><span class="btn">Edit the plan</span></div>
</div>
<div class="row" style="justify-content:flex-end;margin-top:6px;gap:16px"><span class="qlink">Cancel project…</span></div>
<div class="rule" style="margin-top:16px"><b>Suggested:</b> dates and owners for 3 unplanned items, learned from 4 similar projects. Nothing changes until you accept. <a href="#plan">Review the suggestions →</a></div>
<div class="split" style="margin-top:18px">
<div>
<div class="row between"><div class="tabs" style="border:0"><span class="on">Items<sup>8</sup></span><span>History</span></div><div class="seg"><span class="on">Cards</span><span>Sheet</span></div></div>
<div class="prow"><span class="av">AL</span><div class="t">Social cut-down 2 of 6 — Stories</div><div class="meta"><span class="pill t-wait">◷ blocked: client's assets · 4 days</span></div></div>
<div class="prow"><span class="av">AL</span><div class="t">Social cut-down 1 of 6 — Instagram 9:16</div><div class="meta"><span class="pill t-alert">! due today</span></div></div>
<div class="prow"><span class="av">JB</span><div class="t">Hero film — 60s</div><div class="meta"><span class="pill t-wait">◷ with the client until 16 Nov</span></div></div>
<div class="prow"><span class="av">SO</span><div class="t">Social cut-down 3 of 6 — YouTube 16:9</div><div class="meta"><span class="pill t-good">✓ accepted 5 Nov</span><span class="small muted">invoice drafted</span></div></div>
<div class="prow"><span class="av ghost">?</span><div class="t">Launch photography — 1 day</div><div class="meta"><span class="pill">● no owner yet</span><a class="small" href="#plan">Assign</a></div></div>
</div>
<div class="ready">
<div class="row between"><span class="label">Ready to reconcile</span><span class="pill t-alert">! 1 at risk</span></div>
<ul>
<li class="met"><span class="mk" aria-hidden="true">✓</span><span><span class="g">Every item has an owner and a date</span></span></li>
<li><span class="mk"></span><span>Every item accepted or descoped<span class="why">5 of 8 still open. The window ends 30 Nov; what's open then counts as short.</span><a class="fix" href="#board">See the open items →</a></span></li>
</ul>
<p class="small muted" style="margin:10px 0 0">Reconciling is automatic when the window ends. Nobody presses it.</p>
</div>
</div>
</div>
</div>
</div>
<div>
<div class="frame-cap">Phone</div>
<div class="phone touch"><div class="notch"></div><div class="wf">
<div class="kick">Project · Harbor & Pine</div><div class="ttl">Spring launch</div>
<div class="lineage" style="margin-top:10px"><span class="n">Agreement <em>signed</em></span><span class="sep">›</span><span class="n cur">Project <em>active</em></span><span class="sep">›</span><span class="n">Invoices <em>2</em></span></div>
<div class="figs" style="margin-top:12px"><div><span class="label">Delivered</span><b>3/8</b><small>accepted</small></div><div><span class="label">At risk</span><b>1</b><small>ends 30 Nov</small></div></div>
<ol class="stepper" style="margin-top:14px"><li class="done"><span class="dot">✓</span>Plan</li><li class="cur"><span class="dot"></span>Work</li><li><span class="dot"></span>Review</li><li><span class="dot"></span>Reconcile</li></ol>
<div style="display:grid;gap:8px;margin-top:14px"><span class="btn primary">Plan the photography day</span><span class="btn">Edit the plan</span></div>
<div class="label" style="margin-top:16px">Items · 8</div>
<div class="prow"><span class="av lg">AL</span><div class="t">Cut-down 2 of 6</div><div class="meta"><span class="pill t-wait">◷ blocked · 4 days</span></div></div>
<div class="prow"><span class="av lg">JB</span><div class="t">Hero film — 60s</div><div class="meta"><span class="pill t-wait">◷ client to 16 Nov</span></div></div>
<div class="prow"><span class="av lg">SO</span><div class="t">Cut-down 3 of 6</div><div class="meta"><span class="pill t-good">✓ accepted</span></div></div>
</div></div>
</div>
</div>
<div class="ctab-wrap"><table class="ctab">
<thead><tr><th>Component</th><th>Shows</th><th>States</th><th>Action rules</th></tr></thead>
<tbody>
<tr><td data-h="Component">Lineage</td><td data-h="Shows">Quote › contract › project › invoices, each with its state; each a link into its own brick</td><td data-h="States">a link the user can't read shows as plain text (title + state), no link</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Always</span></td></tr>
<tr><td data-h="Component">FiguresRow</td><td data-h="Shows">Delivered x of n · With the client · At risk · Invoiced</td><td data-h="States">Invoiced "—, invoicing not connected" when the composition has no AR brick</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Each filters the items</span></td></tr>
<tr><td data-h="Component">RecordJourney</td><td data-h="Shows">Plan · Work · Review · Reconcile (a reading of planned / active / closed, see Q1) with the next action</td><td data-h="States">closed: stepper complete, primary becomes "Raise a makegood" if reconciled short</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Primary = the most urgent next step THIS user may take (here: an item with no owner); a contributor sees their own item's next step</span><span class="r r-h"><em>hidden</em>Edit the plan without pj:project:plan; Cancel project without pj:project:write or manage</span><span class="r r-b"><em>blocked</em>Raise a makegood before reconciling: "Only a project reconciled short can raise a makegood"</span></td></tr>
<tr><td data-h="Component">Note (AI suggestion)</td><td data-h="Shows">Suggested dates/owners and what they were learned from (PJ-13)</td><td data-h="States">off (absent) · suggest · none to make</td><td data-h="Action rules"><span class="r r-h"><em>hidden</em>Without pj:project:plan, or when the step is off</span></td></tr>
<tr><td data-h="Component">Items (Cards / Sheet)</td><td data-h="Shows">Items ordered by urgency; Sheet = the Plan editor</td><td data-h="States">empty ("No deliverable lines: a sale of only monthly lines plans nothing")</td><td data-h="Action rules"><span class="r r-h"><em>hidden</em>Sheet toggle without pj:item:write</span></td></tr>
<tr><td data-h="Component">Readiness (reconcile)</td><td data-h="Shows">The reconcile gates as the window approaches</td><td data-h="States">all met · at risk (ai_flag_shortfall when on)</td><td data-h="Action rules"><span class="r r-h"><em>hidden</em>No button: reconcile is the system's (pj:project:reconcile is machine-only)</span></td></tr>
</tbody></table></div>
</div>
<!-- B3 PLAN -->
<div class="screen" id="plan">
<div class="label"><span class="sid">projects.plan</span> · stories PJ-01, PJ-13, PJ-20</div>
<h3>The Plan</h3>
<p class="why">The system already made one item per delivered unit sold. The lead names them the way the client will recognise, gives each an owner and a date, and links the work. Nothing from the contract is typed again.</p>
<div class="sign"><div class="row between"><span class="label">Signed parts</span><span class="pill t-info">▸ part of the signature</span></div>
<dl><dt>Purpose</dt><dd>Name, staff and date the items the sale created, without typing anything the contract already says.</dd><dt>Data shown</dt><dd>One row per planned item: name (prefilled from the quote line, source line as hint), owner, due date, work link, billing (read-only from the sale); project window.</dd><dt>Actions → BPMN</dt><dd><ul><li><span class="mono">plan_from_contract</span> the rows themselves (system) · no button</li><li><span class="mono">ai_draft_plan</span> "Accept n suggestions", pj:project:plan · hidden when the step is off</li><li><span class="mono">—</span> rename, assign owner, re-date, link work: item edits, pj:item:write · blocked on done or cancelled rows with the reason</li><li><span class="mono">descope</span> row menu, reason required, pj:item:write · blocked on a done item</li></ul></dd><dt>States</dt><dd>loading · cell confirmed / editing / saving / saved / refused · read-only rows · no deliverable lines (empty)</dd></dl></div>
<div class="pair">
<div>
<div class="frame-cap">Desktop · a Sheet with the owner picker and the date picker open</div>
<div class="desk">
<div class="bar"><b>Deliver</b><span>Board</span><span class="on">Projects</span><span>Reconciliation</span></div>
<div class="body">
<div class="label">Plan · Spring launch</div><div style="font:400 34px/1.05 var(--display);margin-top:4px">8 items from what was sold</div>
<p class="small muted" style="margin:6px 0 14px">Client, prices and billing come from the signed agreement and can't be changed here. Window: 14 Oct – 30 Nov 2026.</p>
<table class="sheet">
<thead><tr><th style="width:36%">Item<span class="sr"> name</span><br><span style="text-transform:none;letter-spacing:0;font-weight:400">what the client will call it</span></th><th>Owner<br><span style="text-transform:none;letter-spacing:0;font-weight:400">who delivers</span></th><th>Due<br><span style="text-transform:none;letter-spacing:0;font-weight:400">your time zone</span></th><th>Work<br><span style="text-transform:none;letter-spacing:0;font-weight:400">Dodil Git</span></th><th>Billed<br><span style="text-transform:none;letter-spacing:0;font-weight:400">from the sale</span></th></tr></thead>
<tbody>
<tr><td>Hero film — 60s<span class="h">from line 2: Video production · 60s</span></td><td><div class="row"><span class="av">JB</span>Jonas</div></td><td><span class="cell-date">2 Nov 2026</span></td><td><a href="#plan">spring-launch#4</a></td><td><span class="pill">on acceptance</span></td></tr>
<tr><td>Social cut-down 1 of 6 — Instagram 9:16<span class="h">from line 3: Social cut-down × 6</span></td><td class="sel"><div class="row"><span class="av">AL</span>Ana <span class="small muted">▾</span></div></td><td><span class="cell-date">9 Nov 2026</span></td><td><a href="#plan">spring-launch#5</a></td><td><span class="pill">on acceptance</span></td></tr>
<tr class="refused"><td>Social cut-down 4 of 6<span class="h">from line 3 · rename it</span></td><td><div class="row"><span class="av">SO</span>Sam</div></td><td><span class="cell-date">28 Nov 2026</span><span class="h" style="color:var(--t-alert-fg)">2 Dec is after the project window (ends 30 Nov). Kept 28 Nov.</span></td><td><span class="qlink">+ Link an issue</span></td><td><span class="pill">on acceptance</span></td></tr>
<tr><td>Launch photography — 1 day<span class="h">from line 4: Photography day</span></td><td><span class="pill t-info">▸ suggested: Ana</span></td><td class="sel"><span class="cell-date">20 Nov 2026 <span class="small muted">suggested</span></span></td><td><span class="qlink">+ Link an issue</span></td><td><span class="pill t-muted">– monthly retainer</span></td></tr>
</tbody>
</table>
<div class="row" style="margin-top:12px;align-items:flex-start;gap:18px">
<div class="pop" style="width:240px" role="listbox" aria-label="Owner">
<div class="label" style="padding:2px 8px 6px">Owner · who delivers</div>
<div class="opt on" role="option" aria-selected="true"><span class="av">AL</span><span class="grow">Ana Lind<br><span class="small muted">4 open items</span></span>✓</div>
<div class="opt" role="option"><span class="av">SO</span><span class="grow">Sam Okafor<br><span class="small muted">2 open items</span></span></div>
<div class="opt" role="option"><span class="av">JB</span><span class="grow">Jonas Berg<br><span class="small muted">6 open items</span></span></div>
<div class="small muted" style="padding:6px 8px 0">Type to filter by name.</div>
</div>
<div class="pop" style="width:260px" role="dialog" aria-label="Due date">
<div class="row between" style="padding:2px 4px 8px"><span class="label">Due date</span><span class="small">‹ November 2026 ›</span></div>
<div class="cal">
<span class="hd">Mo</span><span class="hd">Tu</span><span class="hd">We</span><span class="hd">Th</span><span class="hd">Fr</span><span class="hd">Sa</span><span class="hd">Su</span>
<span class="off">26</span><span class="off">27</span><span class="off">28</span><span class="off">29</span><span class="off">30</span><span class="off">31</span><span>1</span>
<span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span>
<span class="today">9</span><span>10</span><span>11</span><span>12</span><span>13</span><span>14</span><span>15</span>
<span>16</span><span>17</span><span>18</span><span>19</span><span class="on">20</span><span>21</span><span>22</span>
<span>23</span><span>24</span><span>25</span><span>26</span><span>27</span><span>28</span><span>29</span>
<span>30</span><span class="out">1</span><span class="out">2</span><span class="out">3</span><span class="out">4</span><span class="out">5</span><span class="out">6</span>
</div>
<div class="small muted" style="padding:8px 4px 0">Fri 20 Nov 2026 · days after 30 Nov are outside the project window. Type "20 nov" or use arrow keys.</div>
</div>
</div>
<div class="row between" style="margin-top:16px;border-top:1px solid var(--line);padding-top:12px">
<span class="small muted"><span class="kbd">↑↓←→</span> move · <span class="kbd">Enter</span> edits · <span class="kbd">Tab</span> commits · <span class="kbd">Esc</span> cancels · a refused edit restores the old value and says why.</span>
<span class="row"><span class="saving">Saved ✓</span><span class="btn primary">Accept 3 suggestions</span></span>
</div>
</div>
</div>
</div>
<div>
<div class="frame-cap">Phone · rows, then a Drawer per item</div>
<div class="phone touch"><div class="notch"></div><div class="wf">
<div class="kick">Plan · Spring launch</div><div class="ttl">8 items</div>
<div class="prow"><span class="av lg">JB</span><div class="t">Hero film — 60s</div><div class="meta"><span class="pill">2 Nov 2026</span></div></div>
<div class="prow"><span class="av lg">AL</span><div class="t">Cut-down 1 of 6 — Instagram</div><div class="meta"><span class="pill">9 Nov 2026</span></div></div>
<div class="drawer" style="margin:12px -4px 0;border:1px solid var(--line);border-radius:12px">
<div class="label">Edit item</div>
<div class="field"><label>Item name</label><div class="in">Social cut-down 4 of 6</div><span class="hint">From line 3: Social cut-down × 6. Name it the way the client will.</span></div>
<div class="field"><label>Owner</label><div class="in row"><span class="av">SO</span>Sam Okafor ▾</div><span class="hint">Who delivers. They see it on their board.</span></div>
<div class="field"><label>Due date</label><div class="in">28 Nov 2026 ▾</div><span class="err">2 Dec is after the project window (ends 30 Nov). Kept 28 Nov.</span></div>
<div class="field"><label>Work in Dodil Git</label><div class="in muted">Pick an issue ▾</div><span class="hint">Optional. Links the item to the issue the team works in.</span></div>
</div>
</div></div>
</div>
</div>
<div class="ctab-wrap"><table class="ctab">
<thead><tr><th>Component</th><th>Shows</th><th>States</th><th>Action rules</th></tr></thead>
<tbody>
<tr><td data-h="Component">SheetEditor</td><td data-h="Shows">One row per planned item: name (prefilled from the quote line, source line as hint), owner, due, work link, billing (read-only)</td><td data-h="States">cell: confirmed · editing · saving · saved (fades) · refused (old value + sentence) · read-only for done / cancelled rows</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Editable with pj:item:write (lead / manager)</span><span class="r r-b"><em>blocked</em>A done item's row: "Accepted items can't be re-dated"</span><span class="r r-h"><em>hidden</em>Plan screen without pj:item:write and pj:project:plan (read-only items list instead)</span></td></tr>
<tr><td data-h="Component">Picker (owner)</td><td data-h="Shows">People as avatars with name and open-item count; type to filter. Never an email field</td><td data-h="States">loading people · no match ("Nobody called 'Kim' in this team")</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>With pj:item:write</span></td></tr>
<tr><td data-h="Component">DatePicker</td><td data-h="Shows">Labelled calendar, today ringed, days outside the project window struck; accepts typed "20 nov"; one format: 20 Nov 2026</td><td data-h="States">outside window (struck, with reason) · suggested (pill)</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>With pj:item:write</span></td></tr>
<tr><td data-h="Component">Picker (Dodil Git)</td><td data-h="Shows">Issues in the project's repo, by title</td><td data-h="States">no repo linked ("Link a repo in Settings") · see Q3</td><td data-h="Action rules"><span class="r r-h"><em>hidden</em>When the composition has no Git link</span></td></tr>
<tr><td data-h="Component">SaveBar</td><td data-h="Shows">Saved / saving; the primary "Accept n suggestions" when AI suggestions are pending</td><td data-h="States">no suggestions → primary becomes "Done planning" (back to the project)</td><td data-h="Action rules"><span class="r r-h"><em>hidden</em>Accept suggestions when the step is off</span></td></tr>
</tbody></table></div>
</div>
<!-- B4 REVIEW -->
<div class="screen" id="review">
<div class="label"><span class="sid">projects.review</span> · stories PJ-03, PJ-04, PJ-09, PJ-11</div>
<h3>Review and accept an item</h3>
<p class="why">Jonas leads the project and also works on it, so he can both submit and record acceptance. But he submitted this film himself, so the button stays visible and Readiness says why it's blocked and who can do it instead. The client's silence has a clock, and a person sends the warning.</p>
<div class="sign"><div class="row between"><span class="label">Signed parts</span><span class="pill t-info">▸ part of the signature</span></div>
<dl><dt>Purpose</dt><dd>Record the client's verdict on a delivery, with proof, by someone other than who did the work.</dd><dt>Data shown</dt><dd>Item state and revision; the rule in words; submitted date and by whom; the review clock; evidence; readiness for the next transitions.</dd><dt>Actions → BPMN</dt><dd><ul><li><span class="mono">accept</span> "Record acceptance", pj:item:accept · blocked with readiness (evidence, client's answer, submitter ≠ recorder)</li><li><span class="mono">accept_deemed</span> "Record deemed acceptance" replaces it once warned, pj:item:accept · blocked until the notice ends · hidden when deemed acceptance is off</li><li><span class="mono">request_changes</span> "Request changes…", reason required, pj:item:accept</li><li><span class="mono">send_warning</span> pj:item:accept · blocked until the window ends · hidden when deemed acceptance is off</li><li><span class="mono">review</span> the client's answer, recorded through accept / request_changes (external actor, no button)</li><li><span class="mono">descope</span> quiet link, reason required, pj:item:write</li><li><span class="mono">—</span> "+ Attach evidence", pj:item:work or pj:item:write</li></ul></dd><dt>States</dt><dd>in review (window running · warning due · notice running · deemed possible) · done (who, when, how) · stale version · error</dd></dl></div>
<div class="pair">
<div>
<div class="frame-cap">Desktop · as Jonas, who submitted it</div>
<div class="desk">
<div class="bar"><b>Deliver</b><span>Board</span><span class="on">Projects</span><span>Reconciliation</span></div>
<div class="body">
<div class="label">Item · Harbor & Pine · Spring launch</div>
<div style="font:400 40px/1.05 var(--display);margin-top:4px">Hero film — 60s</div>
<div class="lineage" style="margin-top:12px"><span class="n">Agreement, line 2 <em>Video production · 60s</em></span><span class="sep">›</span><span class="n cur">Item <em>in review · rev 1</em></span><span class="sep">›</span><span class="n">Invoice <em>drafted on acceptance</em></span></div>
<div class="journey" style="margin-top:18px">
<ol class="stepper">
<li class="done"><span class="dot">✓</span>Planned</li>
<li class="done"><span class="dot">✓</span>In progress</li>
<li class="cur" aria-current="step"><span class="dot"></span>In review</li>
<li><span class="dot"></span>Done</li>
</ol>
<div class="acts"><span class="btn blocked" aria-describedby="why-accept">Record acceptance</span><span class="btn">Request changes…</span></div>
</div>
<div class="row" style="justify-content:flex-end;margin-top:6px;gap:16px"><span class="qlink">Descope…</span></div>
<div class="split" style="margin-top:18px">
<div>
<div class="rule"><b>Acceptance rule:</b> an item is done when the client accepts it, with evidence attached. Whoever submitted the work doesn't record its acceptance. If the client hasn't answered within 10 working days, the lead may send a warning; 5 working days after it, the item may be recorded as accepted by silence (deemed).</div>
<div class="figs" style="margin-top:16px;grid-template-columns:repeat(3,minmax(0,1fr))">
<div><span class="label">Submitted</span><b>2 Nov</b><small>by Jonas Berg</small></div>
<div><span class="label">With the client</span><b>until 16 Nov</b><small>5 working days left</small></div>
<div><span class="label">Deemed</span><b>23 Nov</b><small>earliest, if warned on 16 Nov</small></div>
</div>
<div class="label" style="margin-top:16px">Evidence</div>
<div class="prow"><span class="pill t-good">✓ link</span><div class="t"><a href="#review">Final cut v3</a> <span class="small muted">· attached 2 Nov by Jonas</span></div></div>
<div class="row" style="margin-top:10px;gap:16px"><span class="qlink">+ Attach evidence</span><span class="btn blocked">Send the deemed-acceptance warning</span><span class="small muted">from 16 Nov, when the window ends</span></div>
</div>
<div class="ready" id="why-accept">
<div class="row between"><span class="label">Ready to record acceptance</span><span class="pill t-warm">◆ 1 to go</span></div>
<ul>
<li class="met"><span class="mk">✓</span><span><span class="g">Submitted for review</span><span class="sr"> (met)</span></span></li>
<li class="met"><span class="mk">✓</span><span><span class="g">Evidence attached: a link, a file or a sign-off</span><span class="sr"> (met)</span></span></li>
<li><span class="mk"></span><span>The client's answer is in<span class="why">Record it when they reply, or after the warning's notice ends.</span></span></li>
<li><span class="mk"></span><span>Someone else records it<span class="why">You submitted this, and your role doesn't allow accepting your own work: Mira Holm (manager) can record the client's acceptance.</span><a class="fix" href="#review">Ask Mira →</a></span></li>
</ul>
</div>
</div>
</div>
</div>
<div class="frame-cap" style="margin-top:18px">Same screen · as Mira, the manager, after the client said yes</div>
<div class="desk"><div class="body">
<div class="journey">
<ol class="stepper"><li class="done"><span class="dot">✓</span>Planned</li><li class="done"><span class="dot">✓</span>In progress</li><li class="cur"><span class="dot"></span>In review</li><li><span class="dot"></span>Done</li></ol>
<div class="acts"><span class="btn primary">Record acceptance</span><span class="btn">Request changes…</span></div>
</div>
<div class="ready" style="margin-top:12px"><div class="row between"><span class="label">Ready to record acceptance</span><span class="pill t-good">✓ ready</span></div><p class="small muted" style="margin:6px 0 0">All four gates met. Recording it asks invoicing for the draft invoice of line 2.</p></div>
</div></div>
</div>
<div>
<div class="frame-cap">Phone · Request changes as a Drawer</div>
<div class="phone touch"><div class="notch"></div><div class="wf">
<div class="kick">Item · Harbor & Pine</div><div class="ttl">Hero film — 60s</div>
<div class="row" style="margin-top:8px"><span class="pill t-wait">◷ with the client until 16 Nov</span></div>
<div style="display:grid;gap:8px;margin-top:12px"><span class="btn blocked">Record acceptance</span><span class="small muted">You submitted this, so someone else accepts: Mira Holm. <a href="#review">Ask Mira</a></span><span class="btn">Request changes…</span></div>
<div class="drawer" style="margin:14px -4px 0;border:1px solid var(--line);border-radius:12px">
<div class="label">Request changes</div>
<div class="field"><label for="rc">What the client wants changed</label><div class="in ta" id="rc"></div><span class="err">Say what should change. The team works from these words.</span></div>
<div class="field"><label>Where they said it</label><div class="in muted">Pick evidence or paste a link ▾</div><span class="hint">Optional. An email, a call note, a comment on the cut.</span></div>
<p class="small muted" style="margin:4px 0 10px">The same item goes back to In progress as revision 2, and the review clock stops. It is never a new item.</p>
<div style="display:grid;gap:8px"><span class="btn primary">Send back to work</span><span class="btn">Cancel</span></div>
</div>
</div></div>
</div>
</div>
<div class="ctab-wrap"><table class="ctab">
<thead><tr><th>Component</th><th>Shows</th><th>States</th><th>Action rules</th></tr></thead>
<tbody>
<tr><td data-h="Component">RecordJourney</td><td data-h="Shows">Item states from process.yaml (renamed by the customer's pj_state rows); blocked shows as a pill on In progress</td><td data-h="States">rev n on the current step after changes · done (who, when, recorded or deemed)</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Record acceptance, Request changes: with pj:item:accept</span><span class="r r-b"><em>blocked</em>Record acceptance while any gate is unmet, with Readiness beside it</span><span class="r r-h"><em>hidden</em>Both for a contributor (pj:item:work only), billing, viewer; Descope without pj:item:write</span></td></tr>
<tr><td data-h="Component">Readiness</td><td data-h="Shows">Gates of <span class="mono">accept</span> (or <span class="mono">accept_deemed</span> once warned): submitted, evidence, the client's answer, submitter ≠ recorder</td><td data-h="States">evaluated by the brick; deemed path swaps the third line for "Warning sent 16 Nov · notice ends 23 Nov"</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>To anyone who can see the action it gates</span></td></tr>
<tr><td data-h="Component">Rule note</td><td data-h="Shows">The acceptance rule in plain words, numbers from the customer's settings (window, notice, deemed on/off)</td><td data-h="States">deemed acceptance off → the silence sentence is dropped</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Always above the action</span></td></tr>
<tr><td data-h="Component">FiguresRow (clock)</td><td data-h="Shows">Submitted · with the client until · deemed earliest (PJ-11); nothing stored, nothing sent</td><td data-h="States">window running (wait) · warning due (warm) · notice running · deemed possible</td><td data-h="Action rules"><span class="r r-b"><em>blocked</em>Send the warning before the window ends: "from 16 Nov, when the window ends"</span><span class="r r-h"><em>hidden</em>Warning when deemed acceptance is switched off</span></td></tr>
<tr><td data-h="Component">SidePanelForm</td><td data-h="Shows">Request changes: the reason (required), where the client said it</td><td data-h="States">invalid (empty reason) · submitting · refused (server sentence) · done (rev 2)</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>With pj:item:accept</span></td></tr>
</tbody></table></div>
</div>
<!-- B5 HISTORY -->
<div class="screen" id="history">
<div class="label"><span class="sid">projects.item_history</span> · the item's History tab</div>
<h3>Item history</h3>
<p class="why">Read top to bottom, it is the story of the delivery: who did what, why it waited, what the client said. Built from the audit log and the workflow events.</p>
<div class="sign"><div class="row between"><span class="label">Signed parts</span><span class="pill t-info">▸ part of the signature</span></div>
<dl><dt>Purpose</dt><dd>The story of one delivery, in sentences a client could read.</dd><dt>Data shown</dt><dd>Audit log and workflow events, grouped by day in the viewer's time zone: time, typed chip, sentence, linked records; quoted reasons.</dd><dt>Actions → BPMN</dt><dd><ul><li><span class="mono">every item key</span> one sentence template per transition key in process.yaml (plan_from_contract … descope) · read-only</li></ul></dd><dt>States</dt><dd>loading · empty (what would appear) · partial (events from sections you can't open are left out) · error</dd></dl></div>
<div class="pair">
<div>
<div class="frame-cap">Desktop</div>
<div class="desk"><div class="body">
<div class="tabs"><span>Overview</span><span class="on">History<sup>9</sup></span></div>
<ul class="tl" style="margin-top:6px">
<li class="day">Fri 6 Nov 2026</li>
<li class="e"><span class="tm">16:40</span><span class="pill t-info">▸ Work</span><span class="s">Jonas Berg submitted revision 2 for review with <a href="#history">Final cut v3</a>. The client has until 20 Nov.</span></li>
<li class="e"><span class="tm">10:05</span><span class="pill t-warm">◆ Changes</span><span class="s">Mira Holm recorded the client's request: “Shorter end card, logo on the left.” Back to work as revision 2.</span></li>
<li class="day">Mon 2 Nov 2026</li>
<li class="e"><span class="tm">14:20</span><span class="pill t-wait">◷ Review</span><span class="s">Jonas Berg submitted it for review with <a href="#history">Final cut v2</a>. The review clock started.</span></li>
<li class="day">Wed 28 Oct 2026</li>
<li class="e"><span class="tm">11:12</span><span class="pill t-info">▸ Work</span><span class="s">The client sent the brand assets. Ana Lind unblocked it after 3 working days on the client's side.</span></li>
<li class="day">Fri 23 Oct 2026</li>
<li class="e"><span class="tm">15:47</span><span class="pill t-wait">◷ Blocked</span><span class="s">Ana Lind blocked it: waiting on the client's brand assets.</span></li>
<li class="day">Wed 14 Oct 2026</li>
<li class="e"><span class="tm">09:02</span><span class="pill">● Plan</span><span class="s">Planned from the signed contract <a href="#history">Spring launch agreement</a>, line 2: Video production · 60s. Jonas Berg named it and gave it to himself, due 2 Nov.</span></li>
</ul>
<p class="small muted" style="margin:12px 0 0">Times in Europe/Stockholm. Invoicing events for this item live in Invoices, which you can open.</p>
</div></div>
</div>
<div>
<div class="frame-cap">Phone</div>
<div class="phone"><div class="notch"></div><div class="wf">
<div class="kick">Hero film — 60s</div><div class="ttl">History</div>
<ul class="tl">
<li class="day">Fri 6 Nov</li>
<li class="e"><span class="tm">16:40</span><span class="pill t-info">▸ Work</span><span class="s">Jonas submitted revision 2 with <a href="#history">Final cut v3</a>.</span></li>
<li class="e"><span class="tm">10:05</span><span class="pill t-warm">◆ Changes</span><span class="s">Mira recorded the client's request: “Shorter end card.”</span></li>
<li class="day">Mon 2 Nov</li>
<li class="e"><span class="tm">14:20</span><span class="pill t-wait">◷ Review</span><span class="s">Jonas submitted it for review.</span></li>
</ul>
<div class="state" style="margin-top:14px"><b>Nothing yet</b><p>On a new item: starting, blocking and submitting show here, with who did it and why.</p></div>
</div></div>
</div>
</div>
<div class="ctab-wrap"><table class="ctab">
<thead><tr><th>Component</th><th>Shows</th><th>States</th><th>Action rules</th></tr></thead>
<tbody>
<tr><td data-h="Component">Timeline</td><td data-h="Shows">Day groups; time · typed chip (tone from the event's transition) · sentence with names and linked documents; quoted reasons</td><td data-h="States">loading · empty (what would appear) · partial ("events from a section you can't open are left out") · error</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>With pj:item:read</span><span class="r r-h"><em>hidden</em>Links to records the user can't read (plain text instead)</span></td></tr>
<tr><td data-h="Component">Sentence templates</td><td data-h="Shows">One per transition key in process.yaml, owned by the brick's ModuleUI; ids never shown</td><td data-h="States">unknown event → "Something changed (blocked_since)" is NOT allowed: fallback "Jonas changed the item" + field names in words</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Read-only</span></td></tr>
</tbody></table></div>
</div>
<!-- B6 NO ACCESS -->
<div class="screen" id="noaccess">
<div class="label"><span class="sid">projects.no_access</span> · opened by URL without pj:project:read or pj:item:read</div>
<h3>No access</h3>
<p class="why">Projects isn't in this person's nav, but they followed a link. Say it in words, name who can grant it, and keep the page's frame so they know where they are.</p>
<div class="sign"><div class="row between"><span class="label">Signed parts</span><span class="pill t-info">▸ part of the signature</span></div>
<dl><dt>Purpose</dt><dd>Tell someone without access where they are and who can grant it.</dd><dt>Data shown</dt><dd>Section name, the team admin by name, where access is granted; the permission only behind "For your admin".</dd><dt>Actions → BPMN</dt><dd><ul><li><span class="mono">—</span> "Copy a request" and "Back" · the Projects nav entry is hidden</li></ul></dd><dt>States</dt><dd>section without access (this) · a record out of scope → not found, same as a dead link (PJ-05)</dd></dl></div>
<div class="pair">
<div>
<div class="frame-cap">Desktop</div>
<div class="desk">
<div class="bar"><b>Sell</b><span>Pipeline</span><span>Quotes</span></div>
<div class="body">
<div class="label">Deliver</div><div style="font:400 40px/1 var(--display);margin-top:4px">Projects</div>
<div class="banner" role="status" style="margin-top:18px"><span class="label">No access</span><span>You don't have access to Projects. Mira Holm, your team admin, can add it on the People page.</span></div>
<div class="row" style="margin-top:14px"><span class="btn">Copy a request for Mira</span><span class="qlink">Back to Pipeline</span></div>
<details style="margin-top:14px;font-size:13px"><summary class="muted" style="min-height:44px;display:flex;align-items:center;cursor:pointer">For your admin</summary><p class="muted" style="margin:0">Needed: <span class="mono">pj:project:read</span> (or a role that includes it, such as Project lead or Viewer).</p></details>
</div>
</div>
</div>
<div>
<div class="frame-cap">Phone</div>
<div class="phone touch"><div class="notch"></div><div class="wf">
<div class="kick">Deliver</div><div class="ttl">Projects</div>
<div class="banner" style="margin-top:12px"><span class="label">No access</span><span>You don't have access to Projects. Ask Mira Holm, your team admin.</span></div>
<div style="display:grid;gap:8px;margin-top:12px"><span class="btn">Copy a request for Mira</span><span class="btn">Back to Pipeline</span></div>
</div></div>
</div>
</div>
<div class="ctab-wrap"><table class="ctab">
<thead><tr><th>Component</th><th>Shows</th><th>States</th><th>Action rules</th></tr></thead>
<tbody>
<tr><td data-h="Component">NoAccess</td><td data-h="Shows">The section in words, the admin by name, where they grant it; the permission only behind "For your admin"</td><td data-h="States">section (this) · single record out of scope → the not-found page, same as a dead link (PJ-05)</td><td data-h="Action rules"><span class="r r-v"><em>visible</em>Copy a request; Back to the user's last section</span><span class="r r-h"><em>hidden</em>The Projects nav entry itself</span></td></tr>
</tbody></table></div>
</div>
</div></section>
<!-- ============ D) QUESTIONS ============ -->
<section class="block" id="questions"><div class="wrap">
<div class="sec-head"><span class="n">D</span><h2>Open questions</h2></div>
<p class="intro"><b>Decided 2026-10-19:</b> 1 keep the friendly stepper, derived by the brick · 2 the brick answers readiness · 3 submitter ≠ recorder becomes a signed gate, unless the role holds <span class="mono">pj:item:accept_own</span> (item v3) · 4 a dedicated <span class="mono">work_ref</span> field · 5 board by state on desktop, urgency list on the phone and as a tab.</p>
<ol class="qs">
<li><div><b>The project stepper: a reading, or the real states?</b><p>The brick's project states are planned · active · closed. "Plan · Work · Review · Reconcile" reads better but Review is derived (items in review), not a state. Keep the friendly reading (derived by the brick and labelled as such), or show the three real states?</p></div></li>
<li><div><b>Who evaluates the gates?</b><p>Readiness must render the brick's own evaluation, never re-derive rules in the UI. That needs a per-record endpoint (e.g. <span class="mono">GET /items/{id}/readiness</span> returning each gate of the next transitions: met, reason, fix link). Add it to every brick's contract, or let the ModuleUI assemble it from fields?</p></div></li>
<li><div><b>Submitter ≠ recorder as a signed gate.</b><p>The spec says it in the manager actor's words ("whoever submitted work never records its acceptance"), but the <span class="mono">accept</span> transition's gate lists only evidence. Promote it to a gate in process.yaml so it appears in the customer's BPMN and in Readiness, as drawn here?</p></div></li>
<li><div><b>The Dodil Git link on an item.</b><p>The item entity has no work-link field today (only evidence). Add a <span class="mono">work_ref</span> attribute to the projects brick, or treat the Git issue as one kind of evidence?</p></div></li>
<li><div><b>Kanban on desktop, urgency list on the phone?</b><p>PJ-08 asks for "overdue first, then today, then this week". Drawn: the desktop Board by state with urgency pills, the phone (and a List tab) by urgency. Or make the urgency list the default everywhere and the Board the tab?</p></div></li>
</ol>
</div></section>
<!-- ============ LOOK (not signed) ============ -->
<section class="block" id="look"><div class="wrap">
<div class="sec-head"><span class="n">E</span><h2>Look</h2><span class="pill t-muted">– not part of the signature</span></div>
<p class="intro">Cosmetic choices erp-ui owns. A theme change never needs a re-sign; a new screen, a new or removed action or field, or a changed flow does.</p>
<div class="fixlist">
<div><b>Type</b><br>Plus Jakarta Sans is the main font: the interface, body text and history sentences. Instrument Serif only for page titles and big figures. (Just Sans can replace Plus Jakarta Sans if self-hosted in erp-ui; it is not on Google Fonts.) 11px tracked uppercase section labels.</div>
<div><b>Colour tokens</b><br>Paper, surface, ink, two secondary inks, rules, one accent for the primary action, seven tone pairs; a light and a dark set, both AA. Defined once on :root.</div>
<div><b>Space and shape</b><br>Paper background, thin rules instead of card headers, few cards, 6–10px radii, a 16px phone gutter, 44px touch targets on the phone.</div>
<div><b>Motion</b><br>Colour transitions only, and a "Saved" that fades. Off under reduced motion.</div>
<div><b>Brandable</b><br>A customer may swap the accent, the fonts and the tone colours. Tone <i>meaning</i> and contrast minimums stay.</div>
</div>
</div></section>
<!-- ============ TO SIGN ============ -->
<section class="block" id="sign"><div class="wrap">
<div class="tosign">
<div class="label">To sign · Projects screens v1</div>
<h3 style="margin-top:6px">Signed like the BPMN: purpose, data, actions and their rules, states, transition keys</h3>
<ul>
<li><span class="box2" aria-hidden="true"></span><span class="sid">projects.board</span><span class="muted small">Board</span></li>
<li><span class="box2" aria-hidden="true"></span><span class="sid">projects.project</span><span class="muted small">Project page</span></li>
<li><span class="box2" aria-hidden="true"></span><span class="sid">projects.plan</span><span class="muted small">The Plan</span></li>
<li><span class="box2" aria-hidden="true"></span><span class="sid">projects.review</span><span class="muted small">Review and accept an item</span></li>
<li><span class="box2" aria-hidden="true"></span><span class="sid">projects.item_history</span><span class="muted small">Item history</span></li>
<li><span class="box2" aria-hidden="true"></span><span class="sid">projects.no_access</span><span class="muted small">No access</span></li>
</ul>
<p class="small muted" style="margin:0">Signed against the projects processes you signed (item v2, project v1). Not signed: section A's shared patterns (erp-ui's own contract) and section E (Look). Needs a re-sign: a new screen, an added or removed action or field, a changed visibility rule, or a changed flow.</p>
</div>
</div></section>
</main>
<footer><div class="wrap">Draft wireframe for review. Invented people, clients and figures. Fonts: Plus Jakarta Sans (main), Instrument Serif (titles and figures).</div></footer>
<script>
(function(){
var b=document.getElementById('theme'), r=document.documentElement;
b.addEventListener('click',function(){
var dark = r.getAttribute('data-theme') ? r.getAttribute('data-theme')==='dark' : window.matchMedia('(prefers-color-scheme: dark)').matches;
r.setAttribute('data-theme', dark ? 'light' : 'dark');
});
})();
</script>
</body>
</html>