feat(browser): a boot failure is a state with a reason, not silence
improvise-1wft and the five beads it blocked were one failure: nothing
carried a boot failure's REASON from the worker to the page. The channel
is one rule -- a protocol frame crosses the port as a STRING, a shell
notice as an OBJECT -- so it needs no protocol change and the
discriminator is total.
A boot failure is a STATE, not a rejected promise. A document another
context holds the exclusive handle to now OPENS read-only: measured first
(harness probe, Chromium + Firefox), getFile() reads the bytes while the
handle is held, with the second-handle refusal asserted so the probe
cannot pass vacuously.
- improvise-1wft the channel: Notice/NoticeKind, boot_notice, take_notice
- improvise-i0v9 read-only boot over vfs::ReadOnly + OpfsFs::read_unattached
- improvise-86nw start() rejecting for any reason now posts BootFailed
- improvise-9cma boot::document_name -- and a/b/c.improv was ACCEPTED as
c.improv, not rejected, which is worse than the report
- improvise-iobc vfs::Origin: a seed error is a notice only when the
document really had to be created without it
- improvise-vep8 the watchdog arms BEFORE transport.ready; its message
stopped claiming the connection was accepted
- improvise-7gip DedicatedWorkerGlobalScope (needs a web-sys feature)
- improvise-zre1 a persist that fails raises a notice; the trigger is the
dirty flag surviving, observed rather than predicted
- improvise-7gf8 the render breaker trips once instead of retrying forever
- improvise-ociy non-OPEN sends are loud; a viewport announcement is not
- improvise-y01k the three ws-server drops are logged
- improvise-r4nf serde failures throw instead of reading as "nothing to send"
Every notice routes through shell.fail, so gotcha 48's disarm half is not
re-broken. Two comments in shell.spec.js stated the opposite of the new
arming order and were corrected rather than left.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
@@ -112,6 +112,43 @@ export function gridPlan(model) {
|
||||
return { cornerSpan, groups, levels: model.col_levels, body };
|
||||
}
|
||||
|
||||
/** What the worker just posted. A protocol frame is a STRING and a shell
|
||||
* notice is an OBJECT; nothing else crosses that port (clients.md). */
|
||||
export function classifyWorkerMessage(data) {
|
||||
if (typeof data === "string") return { kind: "frame", text: data };
|
||||
if (data && typeof data === "object" && typeof data.kind === "string") {
|
||||
return { kind: "notice", notice: data };
|
||||
}
|
||||
return { kind: "unknown", data };
|
||||
}
|
||||
|
||||
/** What a boot or save notice READS AS. The kinds are decided in Rust
|
||||
* (`improvise_worker_server::boot`); only the wording is the page's. */
|
||||
export function noticeText(notice) {
|
||||
const where = notice.document ? ` — ${notice.document}` : "";
|
||||
const why = notice.detail ? ` (${notice.detail})` : "";
|
||||
switch (notice.kind) {
|
||||
case "ReadOnly":
|
||||
return `read-only${where}: another tab or window has it open. Close it and reload to edit.`;
|
||||
case "BootFailed":
|
||||
return `the in-page worker could not start${where}${why}`;
|
||||
case "SeedMissing":
|
||||
return `started an EMPTY document${where}: its starting copy could not be fetched${why}`;
|
||||
case "SaveFailed":
|
||||
return `NOT SAVED${where}${why}`;
|
||||
// A kind this page does not know is still a failure, and saying so beats
|
||||
// saying nothing — the reason it carries is the point (improvise-1wft).
|
||||
default:
|
||||
return `the in-page worker reported ${notice.kind}${where}${why}`;
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether a notice means the session is unusable, as opposed to degraded.
|
||||
* Only `BootFailed` is fatal: read-only, empty and unsaved all still paint. */
|
||||
export function isFatal(notice) {
|
||||
return notice.kind === "BootFailed";
|
||||
}
|
||||
|
||||
/** The page's two status facts and the handshake watchdog, which share a home
|
||||
* because the one rule between them is that a *failure disarms the timer*. */
|
||||
export function createShell({ onStatus, timeoutMs, setTimer = setTimeout, clearTimer = clearTimeout }) {
|
||||
|
||||
Reference in New Issue
Block a user