1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// The `remembered` placement: a signal whose store is the browser's own.
//
// `remembered` is to `client` what `durable` is to `server`. A `client`
// cell is one tab's memory and dies with the tab; this one lives in
// `localStorage`, so it survives a reload, it is shared by every tab of
// the same browser on the same origin, and it is shared with nobody else.
// There is no server in this file and no request: the value never leaves
// the browser it was written in.
//
// # Its own module, and why
//
// §16.3.1: a bundle ships nothing it does not use. A program that declares
// no `remembered` state must not download a `localStorage` wrapper, and
// `list.js`, `foreign.js` and `markup.js` are each here for the same
// reason. `linked_runtime` adds this file exactly when the emitter reached
// it, and `wire.js` with it — see below.
//
// # Why `wire.js` and not `JSON.stringify`
//
// `localStorage` holds strings, and the values that go in are ZD values:
// a `Map of Text to Whole`, a `List of Book`, a record. `JSON.stringify`
// silently turns a `Map` into `{}` — the bug `wire.js` was written to fix,
// and its file comment tells the whole story. This is the same trip a
// `durable` value makes to the store and back, so it is the same encoding,
// from the same file. A second set of rules here is how the two halves of
// a program come to disagree about what `{}` means.
//
// # The key, and why it is prefixed
//
// `zd:` plus the signal's source name. The survey that motivated this
// placement found fifteen keys on one origin, all flat and unprefixed
// (`music-open`, `snake-high-score`, `critterdex`), which is fine until
// two things share an origin. The prefix is not a namespace the program
// can choose, because a key it could choose is a key it could compute, and
// a computed key is a way to read a cell the program did not declare.
//
// # What is deliberately not here
//
// **No `try`/`catch` around the read that swallows a parse failure into
// the initial value.** A stored value that will not decode is a real
// disagreement between what is on disk and what the program now expects,
// and the honest ways to handle it are a migration or a versioned key —
// neither of which the language has yet. What this does instead is
// narrow: it falls back only when the entry is *absent*, which is the one
// case that is not a disagreement at all.
import from './signal.js';
import from './wire.js';
/** Where a `remembered` signal's entry lives, given its source name. */
export
/**
* A signal backed by `localStorage`.
*
* Returns the same `[read, write]` pair `signal` does, so everything
* downstream — `derived`, `effect`, every binding in `dom.js` — treats one
* of these exactly as it treats a `client` cell. That is the point: the
* placement changes where the value is kept and who else can write it, not
* what a reader does with it.
*
* `initial` is the value on a browser that has never run this program.
* It is not a default the read falls back to on every load: once an entry
* exists, the entry is the value, which is what "survives the reload"
* means.
*/
export
/**
* The browser's `localStorage`, or `null` where there is none.
*
* Two hosts have none: the DOM shim the compiler's own tests render
* against, and a browser in a mode where reading the property throws
* rather than returning an object — Safari with cookies blocked has done
* this, and the access itself is what throws, so it cannot be checked with
* `typeof`. In both cases a `remembered` signal degrades to a `client`
* one: it works for the life of the tab and forgets on reload. That is the
* one place this file guesses, and it guesses toward the program still
* running.
*/