Files
improvise/web/index.html
T
Edward Langley 6f9cd6cad5 fix(web): the tile bar, the view panel and the formula panel are drawn
A user reported "I can't expand categories, and the Tile + View UIs don't
work". Driving the assembled site under Chromium separates three different
answers, and the useful finding is that two of the three were missing only
their last layer.

Category expand already worked, by keyboard: Shift-C, j, Enter folds
Command::ToggleCatExpand client-side and the items appear. What does not work
is the mouse, because categoryList builds plain <li>s with no listener — the
whole page's pointer model, filed as improvise-7ybz rather than guessed at.

The tile bar and the view panel had the cache, the keymap, the resolver and
the host round trip, and no DOM. Before this commit the browser held five
TileBarCache entries and both ViewListCache views; Shift-T entered TileSelect,
ArrowRight moved tile_cat_idx, and r/c/p really did move a category between
axes — with nothing on screen to aim them at. RenderModel had no field for
either and index.html had no element. The formula panel was the third of
Panel::ALL in the same state, and is fixed by the same mechanism rather than
left arbitrarily behind.

So RenderModel grows `tiles`, `views` and `formulas`. The two panels follow
the help overlay's rule — an empty list is what says a widget is closed, so
the page asks no question about the mode — and the tile bar is drawn always
with only its *selection* conditional, exactly as src/ui/tile_bar.rs makes it
conditional on AppMode::TileSelect.

The view panel's axis line would have been a second derivation, so
ViewContent::axis_summary moves to improvise_protocol::viewmodel as
view_axis_summary and the TUI's own panel now calls it: one home, both ends
(design-principles §5).

Tests: harness/tests/widgets.spec.js, proved by running it against the
unfixed bundle, where the four new assertions fail on zero elements and the
category one passes — the control that says the keyboard really is being
driven. Two draft assertions were wrong and both corrections are recorded:
Axis::short_label is three columns ("Quarter Pag"), and switching views
leaves the panel open as the TUI does.

Not fixed, filed: improvise-7ybz (no pointer affordance anywhere on the page),
improvise-wsaw (app.js has never read category_panel_open, so #categories is
drawn unconditionally and is now inconsistent with the two panels added here).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 12:18:01 -07:00

91 lines
3.9 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>
<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.
-->
<p id="backend-notice">
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. 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>. <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 id="tiles" aria-label="Tiles"></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>
<div id="help" role="dialog" aria-label="Help" hidden></div>
<script type="module" src="./app.js"></script>
</body>
</html>