d750b433b8
The rule is the deliverable: nothing a user expects to be fixed may move.
Four children of <header> were sized by their own content and all four
changed on the SAME event, so the bar reflowed on a mode change with no
message at all: #mode (mode_label, 4-14 chars), #hint (hint_line, one
line per mode), #doc-name (gains and loses " [+]") and #view-name, which
had :empty { display: none } and so appeared out of nothing.
Every child that changes with a keystroke now has an allocated width, and
#hint is the single elastic child, so nothing is positioned by it. The
mode pill's reserve is a FLOOR and deliberately not a cap: a cap absorbs
an over-long label inside a box that never grows, which is a reserve that
hides its own failure. AppMode's exhaustive-match methods make the
compiler demand mode_label for a new variant; nothing checked the CSS
still fit it. harness/tests/header.spec.js is that check.
The spec found what the ticket did not predict: the compounding one was
not the pill. #status was flex: 0 1 auto beside a flex: 1 1 auto hint, so
a longer hint took the shrink out of #status, which WRAPPED to two and
three lines and grew the header by a row -- 52.5px to 71.0px. Reserving
the pill alone would have left that untouched.
ViewState::status_msg is now a toast in a position: fixed dock anchored at
its bottom, so it takes part in no layout and stacks ABOVE the minibuffer:
the prompt is neither covered nor displaced. composeStatus is gone -- the
two facts go to two elements, so a repaint reaches onModel and cannot
address onShell at all. That is gotcha 46's rule made structural rather
than kept by a join's convention; its unit test was replaced, not deleted.
Dismissal keys off message IDENTITY, not a timer. status_msg is STATE and
repaint() re-offers it on every revision, so a setTimeout alone resurrects
the toast on the next keypress. Cost stated rather than hidden: two
identical messages in a row show once, because nothing carries a sequence
and the alternative reading of an ambiguous offer is exactly the bug.
Nine of seventeen modes are reachable in a browser and the spec names them
individually; three injected faults are recorded in the evidence file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
146 lines
6.2 KiB
HTML
146 lines
6.2 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>improvise</title>
|
|
<link rel="stylesheet" href="./style.css" />
|
|
</head>
|
|
<body>
|
|
<!--
|
|
The browser shell (improvise-cr3, improvise-djm).
|
|
|
|
Deliberately four elements and no framework. Everything that could be
|
|
*wrong* — what a cell is, whether it may be typed into, where the
|
|
selection sits, how the category tree flattens — is decided in Rust and
|
|
arrives already decided (see `render.rs`). This page paints it.
|
|
|
|
Build both wasm bundles first:
|
|
|
|
./web/build.sh
|
|
|
|
then serve this directory over http (a module script will not load from
|
|
file://):
|
|
|
|
python3 -m http.server 8080 --directory web
|
|
|
|
That is the whole standalone deployment: the document lives in an
|
|
in-page Web Worker and persists to this browser's private storage.
|
|
?doc= and ?seed= name the document and the file it is first seeded from.
|
|
|
|
To talk to a separate `improvise-ws-server` process instead — which is
|
|
how a document open in the TUI is shared — pass ?ws=host:port; the
|
|
scheme defaults to wss:// on https and ws:// on http.
|
|
-->
|
|
<header>
|
|
<h1>improvise</h1>
|
|
<!--
|
|
What this page IS: web/assemble.sh rewrites the text below to the tag
|
|
or commit it assembled from (improvise-oujb). It sits with the name
|
|
because the bar's order starts at "what this is", and a tree served
|
|
straight out of web/ was never assembled and honestly says so.
|
|
-->
|
|
<span title="Build this page was assembled from" id="build-version">unstamped</span>
|
|
<span id="doc-name"></span>
|
|
<span id="view-name"></span>
|
|
<span id="mode"></span>
|
|
<span id="hint"></span>
|
|
<span id="status" role="status"></span>
|
|
</header>
|
|
|
|
<!--
|
|
This page is the *client* half and nothing else: it holds no model. What
|
|
it now has is a peer it can always reach — a Web Worker in this same tab
|
|
hosting the whole App over OPFS (improvise-9x6, improvise-djm) — so a
|
|
copy served from a static host, GitHub Pages included, is a working
|
|
application rather than a client with nowhere to connect.
|
|
|
|
The notice below stays static HTML rather than something app.js decides,
|
|
for the same reason as before: it is true on every load in either mode,
|
|
and a page that cannot reach its peer cannot be trusted to explain why.
|
|
What the peer is actually *doing* — starting, connected, unreachable,
|
|
gone — is live, and app.js writes it to #status.
|
|
|
|
It is a <details>, so the collapsing is the BROWSER's — keyboard
|
|
operable, and correct with app.js absent or still loading. The only
|
|
thing app.js adds is remembering the answer; the one line every visitor
|
|
needs is the <summary>, which never collapses.
|
|
-->
|
|
<details id="backend-notice">
|
|
<summary>
|
|
<span class="notice-lede"
|
|
>Everything here runs in this tab — the document lives in a Web
|
|
Worker and is saved to this browser's private storage, so edits
|
|
survive a reload and never leave the machine.</span
|
|
>
|
|
<span class="notice-toggle" aria-hidden="true"></span>
|
|
</summary>
|
|
<div class="notice-body">
|
|
<p>
|
|
Load a different document with <code>?doc=name.improv</code>, seeded
|
|
from <code>?seed=url</code>. To open a document hosted by a separate
|
|
process instead, run
|
|
<code>improvise-ws-server model.improv --port 8787</code> and reload
|
|
with <code>?ws=host:port</code>.
|
|
</p>
|
|
<p>
|
|
<strong>The scheme defaults to <code>wss://</code></strong>, because
|
|
this page is served over https and an https origin refuses a
|
|
<code>ws://</code> address as mixed content. Note that
|
|
<code>improvise-ws-server</code> speaks plain <code>ws://</code>
|
|
only, so reaching it from <em>this</em> page means terminating TLS in
|
|
front of it with a reverse proxy or a tunnel — the README has a
|
|
worked Caddy example. Served over <code>http://localhost</code> the
|
|
same page defaults to <code>ws://</code> and pairs with it directly,
|
|
with nothing in between; that is the case the pairing is really for.
|
|
Sharing a document is what the server is for — not running one is
|
|
what everything above is for.
|
|
</p>
|
|
</div>
|
|
</details>
|
|
|
|
<div id="tiles" aria-label="Tiles"></div>
|
|
|
|
<!--
|
|
The page bar: which item each Page or Filter category is fixed to. The
|
|
TUI draws it inside the grid's border; here it is a line of its own,
|
|
above #grid, so it is not charged to the grid's own frame cost.
|
|
-->
|
|
<div id="pages" aria-label="Page axes" hidden></div>
|
|
|
|
<!--
|
|
The page menu (improvise-rw6l). It lives OUTSIDE #pages because that
|
|
element's children are replaced on every repaint, and a menu rebuilt
|
|
under the pointer is a menu that closes itself.
|
|
-->
|
|
<div id="page-menu" role="listbox" aria-label="Page items" hidden></div>
|
|
|
|
<main>
|
|
<aside id="categories" aria-label="Categories"></aside>
|
|
<aside id="formulas" aria-label="Formulas" hidden></aside>
|
|
<aside id="views" aria-label="Views" hidden></aside>
|
|
<div id="grid" tabindex="0" aria-label="Grid"></div>
|
|
</main>
|
|
|
|
<!--
|
|
The bottom dock (improvise-n6t8). It is `position: fixed`, so nothing
|
|
inside it takes part in the page's layout and a message cannot push the
|
|
header or the grid. It is anchored at its BOTTOM and stacks upward, so
|
|
the minibuffer sits where it always sat and a toast appears above it:
|
|
the prompt is never covered and never displaced. context/code/clients.md.
|
|
|
|
#toast keeps its text when it hides — the element is the record of the
|
|
last message shown, and an emptied one could not tell "nothing was said"
|
|
from "something else was said".
|
|
-->
|
|
<div id="bottom-dock">
|
|
<div id="toast" role="status" aria-live="polite" hidden></div>
|
|
<div id="minibuffer" role="status" hidden></div>
|
|
</div>
|
|
|
|
<div id="help" role="dialog" aria-label="Help" hidden></div>
|
|
|
|
<script type="module" src="./app.js"></script>
|
|
</body>
|
|
</html>
|