docs: turn the accepted panel design into an enforceable contract

panel-redesign.md is rewritten for the accepted direction: three layers
(Bulma as shipped, adapters, own sp- components), the page layouts, what
an implementer must not do, and how that is enforced — guard tests that
land before any template, a design-first CI step, and acceptance by
evidence (test output plus a screenshot of every page beside its mockup,
reviewed by a model that did not write the step).

A components page shows every building block in every state; a screen
may be built from nothing else. panel/check.py lints the mockups against
the contract (class vocabulary, CSS bans on !important, id and page
selectors and stray widths, page structure) and writes
panel/outlines.json, the per-screen component skeleton an implementation
is compared with, so a different design cannot pass as this one.

Own modifier classes now carry the sp- prefix (sp-warn, sp-fail, sp-big,
sp-danger-zone, ...) so the vocabulary is machine-checkable; the lint
also caught a width set outside the layout in the mockup's own CSS.

Agent rule .cursor/rules/panel-design.mdc; roadmap row and section
updated (0/15, still candidate).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
mix
2026-09-21 20:55:18 +03:00
co-authored by Claude Fable 5.1
parent d1f22fbcc8
commit 1bcfca1586
38 changed files with 1402 additions and 256 deletions
@@ -72,16 +72,16 @@
<div class="box">
<div class="sp-box-head"><span class="sp-no">01</span><h2>Recent lines</h2><div class="sp-end">Last 200 · refreshes every 10 s</div></div>
<pre class="sp-log"><span class="d">Sep 21 14:01:52</span> postfix/smtp[2131]: 4XcB9q0Lw2z1: to=&lt;m.keller@gmx.de&gt;, relay=mx00.gmx.net[212.227.15.9]:25, delay=0.8, status=sent (250 Requested mail action okay)
<span class="d">Sep 21 14:01:52</span> postfix/qmgr[58]: 4XcB9q0Lw2z1: removed
<span class="d">Sep 21 14:00:18</span> postfix/smtpd[2129]: connect from mail.uni.example[192.0.2.77]
<span class="d">Sep 21 14:00:18</span> postfix/smtp[2130]: 4XcB8z5Tn1z1: to=&lt;dev@lists.example.org&gt;, relay=mx.internal[10.0.4.2]:25, status=sent (250 2.0.0 Ok: queued)
<span class="d">Sep 21 13:58:09</span> <span class="w">postfix/smtp[2123]: 4XcB7k2Jm9z1: to=&lt;noc@example.org&gt;, relay=mx1.example.org[192.0.2.10]:25, status=deferred (451 4.7.1 Greylisted, try again in 5 minutes)</span>
<span class="d">Sep 21 13:57:41</span> <span class="e">postfix/smtpd[2119]: NOQUEUE: reject: RCPT from bulk.example[198.51.100.90]: 550 5.1.1 &lt;sales@lists.example.org&gt;: Recipient address rejected: unknown recipient</span>
<span class="d">Sep 21 13:44:02</span> <span class="w">postfix/smtp[2101]: 4XcB2m7Hd4z1: to=&lt;jobs@acme.io&gt;, relay=none, status=deferred (connect to 10.0.4.12[10.0.4.12]:2525: Connection timed out)</span>
<span class="d">Sep 21 13:41:19</span> <span class="e">postfix/smtp[2098]: 4XcB1c3Rk8z1: to=&lt;j.doe@nonexistent.example&gt;, status=bounced (Host or domain name not found)</span>
<span class="d">Sep 21 13:40:02</span> <span class="w">journal-milter[40]: rate limit: app=prod-server 201/200 in 3600s — tempfail at MAIL FROM</span>
<span class="d">Sep 21 13:00:00</span> opendkim[39]: key table reloaded (3 domains)</pre>
<pre class="sp-log"><span class="sp-d">Sep 21 14:01:52</span> postfix/smtp[2131]: 4XcB9q0Lw2z1: to=&lt;m.keller@gmx.de&gt;, relay=mx00.gmx.net[212.227.15.9]:25, delay=0.8, status=sent (250 Requested mail action okay)
<span class="sp-d">Sep 21 14:01:52</span> postfix/qmgr[58]: 4XcB9q0Lw2z1: removed
<span class="sp-d">Sep 21 14:00:18</span> postfix/smtpd[2129]: connect from mail.uni.example[192.0.2.77]
<span class="sp-d">Sep 21 14:00:18</span> postfix/smtp[2130]: 4XcB8z5Tn1z1: to=&lt;dev@lists.example.org&gt;, relay=mx.internal[10.0.4.2]:25, status=sent (250 2.0.0 Ok: queued)
<span class="sp-d">Sep 21 13:58:09</span> <span class="sp-w">postfix/smtp[2123]: 4XcB7k2Jm9z1: to=&lt;noc@example.org&gt;, relay=mx1.example.org[192.0.2.10]:25, status=deferred (451 4.7.1 Greylisted, try again in 5 minutes)</span>
<span class="sp-d">Sep 21 13:57:41</span> <span class="sp-e">postfix/smtpd[2119]: NOQUEUE: reject: RCPT from bulk.example[198.51.100.90]: 550 5.1.1 &lt;sales@lists.example.org&gt;: Recipient address rejected: unknown recipient</span>
<span class="sp-d">Sep 21 13:44:02</span> <span class="sp-w">postfix/smtp[2101]: 4XcB2m7Hd4z1: to=&lt;jobs@acme.io&gt;, relay=none, status=deferred (connect to 10.0.4.12[10.0.4.12]:2525: Connection timed out)</span>
<span class="sp-d">Sep 21 13:41:19</span> <span class="sp-e">postfix/smtp[2098]: 4XcB1c3Rk8z1: to=&lt;j.doe@nonexistent.example&gt;, status=bounced (Host or domain name not found)</span>
<span class="sp-d">Sep 21 13:40:02</span> <span class="sp-w">journal-milter[40]: rate limit: app=prod-server 201/200 in 3600s — tempfail at MAIL FROM</span>
<span class="sp-d">Sep 21 13:00:00</span> opendkim[39]: key table reloaded (3 domains)</pre>
</div>
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>