@bongos/core 1.19.1011 → 1.19.1013
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +53 -43
- package/docs/adr/0331-the-sky-is-the-ideators-home.md +6 -1
- package/docs/copy-inventory.md +99 -56
- package/docs/copy-registry.json +830 -465
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/idea-objects.js +4 -1
- package/modules/hall-ui/public/sky-composer.js +155 -0
- package/modules/hall-ui/public/sky-data.js +287 -0
- package/modules/hall-ui/public/sky-draw.js +244 -0
- package/modules/hall-ui/public/sky-interaction.js +137 -0
- package/modules/hall-ui/public/sky-layout.js +81 -0
- package/modules/hall-ui/public/sky-panel.js +141 -0
- package/modules/hall-ui/public/sky-tour.js +80 -0
- package/modules/hall-ui/public/thinking.css +203 -267
- package/modules/hall-ui/public/thinking.html +119 -58
- package/modules/hall-ui/public/thinking.states.json +33 -9
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/gen-file-map.js +29 -12
- package/src/bongos/routes/me.js +4 -1
- package/src/bongos/serve-internal.js +20 -6
- package/src/module-api.js +1 -1
- package/tests/file_map.mjs +35 -3
- package/tests/hall_audit.mjs +27 -0
- package/tests/hall_page_gate_map.mjs +11 -5
- package/tests/hall_sky.mjs +393 -405
- package/tests/hall_thinking_room.mjs +274 -296
- package/tests/helpers.mjs +185 -217
- package/tests/ideator_large_dead_thought_proof.mjs +33 -25
- package/tests/ideator_traceable_proof.mjs +61 -39
- package/modules/hall-ui/public/sky.css +0 -365
- package/modules/hall-ui/public/sky.html +0 -79
- package/modules/hall-ui/public/sky.js +0 -580
- package/modules/hall-ui/public/sky.states.json +0 -20
- package/modules/hall-ui/public/thinking.js +0 -418
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
import { strict as assert } from 'node:assert';
|
|
45
45
|
import { createRequire } from 'node:module';
|
|
46
|
-
import { makeRunner, makeSqlAwareClient,
|
|
46
|
+
import { makeRunner, makeSqlAwareClient, bootSkyPage, settle } from './helpers.mjs';
|
|
47
47
|
|
|
48
48
|
process.env.NODE_ENV = 'test';
|
|
49
49
|
|
|
@@ -182,13 +182,22 @@ async function anHourLater(filed) {
|
|
|
182
182
|
return { ideas, total: ideas.length, limit: 50, offset: 0 };
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
// Boots the REAL room
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
185
|
+
// Boots the REAL room — since task 1004231 the sky's list drawer, one key away
|
|
186
|
+
// — over a REAL trail response (the rows GET /sky/mine hands back whole) and
|
|
187
|
+
// hands back the page with the drawer open, so `.rows()` is the HTML a reader
|
|
188
|
+
// is actually shown. No receipt is reachable from here: bootSkyPage's
|
|
189
|
+
// localStorage holds nothing of the filing, which is the "another device, an
|
|
190
|
+
// hour later" condition this proof is about. The live goal is a well in the
|
|
191
|
+
// sky because the routed filing minted a task in it — GET /sky's wells are the
|
|
192
|
+
// goals carrying at least one task — which is what lets the row NAME it.
|
|
189
193
|
async function room(page) {
|
|
190
|
-
const p =
|
|
191
|
-
|
|
194
|
+
const p = bootSkyPage({
|
|
195
|
+
mine: { builder_id: FILER, ideas: page.ideas, total: page.total, cap: 1000, truncated: false },
|
|
196
|
+
sky: { goals: [{ goal_id: String(LIVE_GOAL.id), title: 'The live goal', subtitle: null, status: 'open', version_id: LIVE_GOAL.version_id, category: null, task_total: 1, task_done: 0, created_at: '2026-08-01T00:00:00.000Z', criteria: [], tasks: [{ id: '9001', title: THOUGHT, status: 'backlog', credits_reward: 0, created_on: '2026-09-01', idea_id: null }] }], edges: [], counts: {}, cache: {} },
|
|
197
|
+
viewer: { id: FILER, permissions: {} },
|
|
198
|
+
});
|
|
199
|
+
for (let i = 0; i < 30; i += 1) await settle();
|
|
200
|
+
p.key('l');
|
|
192
201
|
return p;
|
|
193
202
|
}
|
|
194
203
|
|
|
@@ -213,15 +222,23 @@ await test('aimed: filing one line lands it as work, and the hall is told so at
|
|
|
213
222
|
await test('aimed: AN HOUR LATER the room still names what it became, with no receipt in reach', async () => {
|
|
214
223
|
const filed = await fileIt({ goalId: LIVE_GOAL.id });
|
|
215
224
|
const page = await anHourLater(filed);
|
|
216
|
-
const
|
|
225
|
+
const p = await room(page);
|
|
226
|
+
const rows = p.rows();
|
|
217
227
|
|
|
218
228
|
assert.match(rows, /the filing receipt does not survive a reload/, 'the thought is on the page');
|
|
219
|
-
// "What became of it" — the task,
|
|
220
|
-
// the
|
|
221
|
-
|
|
222
|
-
|
|
229
|
+
// "What became of it" — the task, counted in the row and NAMED in the panel
|
|
230
|
+
// (the sky's drawer says "1 task"; its panel lists the descent by title, and
|
|
231
|
+
// the record is one door away). This is the answer the 2026-09-19 filing
|
|
232
|
+
// could not get.
|
|
233
|
+
assert.match(rows, /1 task/, 'the row says a task fell out of it');
|
|
223
234
|
assert.doesNotMatch(rows, /nothing yet/,
|
|
224
235
|
'a thought that became work is never drawn as one that caused none');
|
|
236
|
+
const i = p.S().ideas.find((x) => x.id === String(page.ideas[0].id));
|
|
237
|
+
p.SKY().pick({ kind: 'idea', ref: i }, false);
|
|
238
|
+
assert.match(p.panel(), /the descent — everything that fell out of it/);
|
|
239
|
+
assert.match(p.panel(), new RegExp(`<span class="st">backlog</span><span>${THOUGHT}`),
|
|
240
|
+
'the panel names the task the thought became, and its state');
|
|
241
|
+
assert.match(p.panel(), new RegExp(`href="/idea/${page.ideas[0].id}"`), 'and the record is a door away');
|
|
225
242
|
});
|
|
226
243
|
|
|
227
244
|
await test('aimed: the goal that caught it survives the hour — the room attributes it to the SAME well', async () => {
|
|
@@ -256,10 +273,15 @@ await test('floated: an hour later the room says nothing has claimed it — in t
|
|
|
256
273
|
assert.equal(st.state, 'protostar', 'filed, not yet ignited');
|
|
257
274
|
assert.equal(st.position, 'open-space', 'and nothing caught it');
|
|
258
275
|
|
|
259
|
-
const
|
|
276
|
+
const p = await room(page);
|
|
277
|
+
const rows = p.rows();
|
|
260
278
|
assert.match(rows, /the filing receipt does not survive a reload/, 'the thought is still on the page');
|
|
261
|
-
assert.match(rows,
|
|
262
|
-
|
|
279
|
+
assert.match(rows, /· open space/, 'the row says it floats');
|
|
280
|
+
const i = p.S().ideas.find((x) => x.id === String(page.ideas[0].id));
|
|
281
|
+
assert.equal(p.SKY().positionWord(i), 'nothing has claimed it');
|
|
282
|
+
p.SKY().pick({ kind: 'idea', ref: i }, false);
|
|
283
|
+
assert.match(p.panel(), /nothing has claimed it/,
|
|
284
|
+
'the panel says so in the reader\'s words, not by leaving the fact blank');
|
|
263
285
|
});
|
|
264
286
|
|
|
265
287
|
// =========================================================================
|
|
@@ -275,41 +297,41 @@ await test('the two outcomes do not render alike — a caught thought and a floa
|
|
|
275
297
|
const floatRows = (await room(await anHourLater(await fileIt({ goalId: null })))).rows();
|
|
276
298
|
|
|
277
299
|
assert.notEqual(aimedRows, floatRows, 'the two rows are not the same HTML');
|
|
278
|
-
assert.match(aimedRows, /
|
|
279
|
-
assert.match(floatRows, /
|
|
280
|
-
// …and
|
|
281
|
-
//
|
|
282
|
-
|
|
300
|
+
assert.match(aimedRows, /class="mk protostar"/, 'both are protostars — neither has been paid yet');
|
|
301
|
+
assert.match(floatRows, /class="mk protostar"/);
|
|
302
|
+
// …and WHERE IT SITS is what separates them, which is exactly the position
|
|
303
|
+
// axis idea-objects.js says is load-bearing for a protostar alone: the caught
|
|
304
|
+
// one is in its well, the floating one in open space.
|
|
305
|
+
assert.match(aimedRows, /· in The live goal/, 'the caught thought names the well that caught it');
|
|
306
|
+
assert.doesNotMatch(aimedRows, /· open space/,
|
|
283
307
|
'the caught thought must never be described as unclaimed');
|
|
284
|
-
assert.match(floatRows,
|
|
308
|
+
assert.match(floatRows, /· open space/);
|
|
309
|
+
assert.doesNotMatch(floatRows, /· in The live goal/);
|
|
285
310
|
});
|
|
286
311
|
|
|
287
312
|
// =========================================================================
|
|
288
313
|
// 4. HOW FAR THE CHAIN GETS BEFORE A GOAL IS NAMED — recorded, not assumed
|
|
289
314
|
// =========================================================================
|
|
290
315
|
|
|
291
|
-
await test('the room
|
|
292
|
-
//
|
|
293
|
-
// "
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
// positionWord deliberately returns "has a home" / "waiting for a nod" and
|
|
298
|
-
// never the well's identity).
|
|
299
|
-
//
|
|
300
|
-
// That is a rendering gap, not a traceability one, so it is recorded here
|
|
301
|
-
// rather than fixed under a verify task (it is task 1004225) — and pinned, so that when the room
|
|
302
|
-
// does start naming the well this assertion fails and is deleted along with
|
|
303
|
-
// the follow-up it points at.
|
|
316
|
+
await test('the room NAMES the well that caught it — the gap task 1004225 recorded is closed by the sky', async () => {
|
|
317
|
+
// The previous version of this case pinned the OPPOSITE: the old room printed
|
|
318
|
+
// the position ("has a home") and never the well's identity, and said so
|
|
319
|
+
// here so that the day the room started naming the well, the assertion would
|
|
320
|
+
// fail and be replaced. That day is task 1004231: the sky's drawer says
|
|
321
|
+
// "· in <the well>" on the row, and its panel's gravity fact links the goal.
|
|
304
322
|
const filed = await fileIt({ goalId: LIVE_GOAL.id });
|
|
305
323
|
const page = await anHourLater(filed);
|
|
306
|
-
const
|
|
324
|
+
const p = await room(page);
|
|
325
|
+
const rows = p.rows();
|
|
307
326
|
|
|
308
327
|
assert.ok(page.ideas[0].state.wellGoalId, 'the well IS resolved and delivered to the page');
|
|
309
|
-
assert.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
328
|
+
assert.equal(String(page.ideas[0].state.wellGoalId), String(LIVE_GOAL.id));
|
|
329
|
+
assert.match(rows, /· in The live goal/, 'the row names the well by its title');
|
|
330
|
+
const i = p.S().ideas.find((x) => x.id === String(page.ideas[0].id));
|
|
331
|
+
p.SKY().pick({ kind: 'idea', ref: i }, false);
|
|
332
|
+
assert.match(p.panel(), new RegExp(`gravity</span><span class="v"><a href="#" data-goal="${LIVE_GOAL.id}">The live goal</a>`),
|
|
333
|
+
'and the panel makes the well a door');
|
|
334
|
+
assert.match(p.panel(), /waiting for a nod/, 'while still saying what its position means');
|
|
313
335
|
});
|
|
314
336
|
|
|
315
337
|
summary();
|
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
/* sky.css — what THE SKY has that no other hall page has
|
|
2
|
-
(task 1004105 / BV2.R26, goal 1000095).
|
|
3
|
-
|
|
4
|
-
The composition is the shell's: `.page-head--board`, `.scroll` and
|
|
5
|
-
`.btn-ghost` come from style.css and are not restated here. What is left is
|
|
6
|
-
the four shapes only this page has — the well, its criterion rings, the
|
|
7
|
-
thought glyph in five objects, and the panel beside the field.
|
|
8
|
-
|
|
9
|
-
THE FIVE MARKS ARE THE ROOM'S, ONE SIZE UP. thinking.css draws the same five
|
|
10
|
-
objects at a fixed 9px because a table row has no room to say size in the
|
|
11
|
-
mark and says it in a bar instead. Here the mark IS the size channel, so the
|
|
12
|
-
geometry is parameterised by `--d` and everything else about it — filled /
|
|
13
|
-
haloed / orbited / hollow / hairline-edged — is deliberately the same
|
|
14
|
-
vocabulary. A reader who learned the five in the room must not have to learn
|
|
15
|
-
them again here, which is the same reason `idea-objects.js` is shared.
|
|
16
|
-
|
|
17
|
-
NO COLOUR THAT IS NOT A TOKEN. Every value below is one of the world's
|
|
18
|
-
fifteen or a color-mix of them, so the page inherits dark and light from the
|
|
19
|
-
pack rather than declaring either: there is not one mode-specific rule in
|
|
20
|
-
this sheet, which is why it needs no dark twin. `--line` is a WIDTH; a
|
|
21
|
-
hairline's colour is `--rule` / `--rule-soft`.
|
|
22
|
-
|
|
23
|
-
NO MOTION. Nothing here transitions or animates, so the sheet sits entirely
|
|
24
|
-
inside the Kill Switch rather than being excused by it. A field of a
|
|
25
|
-
person's own thinking that twinkles is a toy; this one is a measurement. */
|
|
26
|
-
|
|
27
|
-
/* ---- the layout ----------------------------------------------------------
|
|
28
|
-
Field and panel side by side on a desk, stacked on a phone. The panel is
|
|
29
|
-
fixed-width because its job is to hold a sentence at a readable measure
|
|
30
|
-
whatever the field is doing beside it. */
|
|
31
|
-
.sky-wrap {
|
|
32
|
-
display: grid;
|
|
33
|
-
grid-template-columns: minmax(0, 1fr) 320px;
|
|
34
|
-
gap: var(--sp-lg);
|
|
35
|
-
align-items: start;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/* ---- the wells -----------------------------------------------------------
|
|
39
|
-
One goal, one cell. The mock draws a single continuous starfield of absolute
|
|
40
|
-
coordinates; that reading survives 1280px and dies at 320, where four wells
|
|
41
|
-
overlap into one smear. `auto-fit` keeps one well per row on a phone and
|
|
42
|
-
four across on a desk with no second set of numbers. */
|
|
43
|
-
.sky-field-grid {
|
|
44
|
-
display: grid;
|
|
45
|
-
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
46
|
-
gap: var(--sp-lg) var(--sp-md);
|
|
47
|
-
margin: 0 0 var(--sp-md);
|
|
48
|
-
}
|
|
49
|
-
.sky-well { min-width: 0; }
|
|
50
|
-
.sky-well__h {
|
|
51
|
-
margin: 0;
|
|
52
|
-
font-family: var(--label-face);
|
|
53
|
-
font-size: var(--label-size);
|
|
54
|
-
letter-spacing: var(--label-space);
|
|
55
|
-
text-transform: var(--label-case);
|
|
56
|
-
font-weight: 400;
|
|
57
|
-
color: var(--ink-soft);
|
|
58
|
-
}
|
|
59
|
-
.sky-well__h a {
|
|
60
|
-
display: inline-block;
|
|
61
|
-
min-height: 24px;
|
|
62
|
-
padding: 3px 0;
|
|
63
|
-
line-height: 18px;
|
|
64
|
-
color: var(--ink-soft);
|
|
65
|
-
border-bottom: var(--line) solid var(--rule-soft);
|
|
66
|
-
}
|
|
67
|
-
.sky-well__h a:hover { color: var(--ink); border-bottom-color: var(--ink); text-decoration: none; }
|
|
68
|
-
.sky-well__note {
|
|
69
|
-
margin: 2px 0 var(--sp-hair);
|
|
70
|
-
font-size: 12px;
|
|
71
|
-
color: var(--ink-faint);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/* The well itself. `--surface-2` and not a literal black: the direction's void
|
|
75
|
-
is the DARK mode of this pack, and a page that hard-coded it would be a hole
|
|
76
|
-
in a light instance. The recessed surface is the same shape in both. */
|
|
77
|
-
.sky-field {
|
|
78
|
-
position: relative;
|
|
79
|
-
aspect-ratio: 1 / 1;
|
|
80
|
-
border-radius: 50%;
|
|
81
|
-
background: var(--surface-2);
|
|
82
|
-
/* The well's own edge. Faint, because the rings inside it are the thing
|
|
83
|
-
carrying meaning and an outer border competing with them would read as a
|
|
84
|
-
sixth criterion. */
|
|
85
|
-
box-shadow: inset 0 0 0 var(--line) var(--rule-soft);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/* ---- the criterion rings -------------------------------------------------
|
|
89
|
-
"A goal's criteria are the rings inside its well, closing as each is
|
|
90
|
-
satisfied." Closed is a solid hairline in `--ink-faint`; open is the softer
|
|
91
|
-
rule. `--ink-faint` and not `--rule` for the closed one is the finding the
|
|
92
|
-
room's remnant glyph recorded: at the hairline's weight `--rule` measures
|
|
93
|
-
under the 3:1 non-text floor on the dark ground, and a satisfied criterion is
|
|
94
|
-
exactly the thing that must read. */
|
|
95
|
-
.sky-ring {
|
|
96
|
-
position: absolute;
|
|
97
|
-
left: 50%;
|
|
98
|
-
top: 50%;
|
|
99
|
-
width: var(--r);
|
|
100
|
-
height: var(--r);
|
|
101
|
-
transform: translate(-50%, -50%);
|
|
102
|
-
border-radius: 50%;
|
|
103
|
-
border: var(--line) dashed var(--rule-soft);
|
|
104
|
-
}
|
|
105
|
-
.sky-ring[data-closed="1"] { border-style: solid; border-color: var(--ink-faint); }
|
|
106
|
-
|
|
107
|
-
/* ---- a thought -----------------------------------------------------------
|
|
108
|
-
The BUTTON is the 24px floor and the GLYPH is the drawing. They are two
|
|
109
|
-
elements on purpose: a 9px cold body is the right mark and a 9px hit target
|
|
110
|
-
is not a control, so the target is sized for a hand and the mark for the
|
|
111
|
-
truth. Centred on its point, so `left`/`top` mean the thought's position and
|
|
112
|
-
not the corner of its box. */
|
|
113
|
-
.sky-body {
|
|
114
|
-
position: absolute;
|
|
115
|
-
transform: translate(-50%, -50%);
|
|
116
|
-
display: flex;
|
|
117
|
-
align-items: center;
|
|
118
|
-
justify-content: center;
|
|
119
|
-
width: 34px;
|
|
120
|
-
height: 34px;
|
|
121
|
-
padding: 0;
|
|
122
|
-
margin: 0;
|
|
123
|
-
border: 0;
|
|
124
|
-
border-radius: 50%;
|
|
125
|
-
background: transparent;
|
|
126
|
-
cursor: pointer;
|
|
127
|
-
}
|
|
128
|
-
/* The hover halo is mixed from the ACCENT and not from `--ink`: `--ink` is
|
|
129
|
-
#17181a in light and #ffffff in dark, so a flat wash of it over whatever is
|
|
130
|
-
behind turns white and LIFTS the field in dark instead of marking the target
|
|
131
|
-
(tests/hall_audit.mjs, "no rule washes a whole surface with a ground token" —
|
|
132
|
-
the same inversion .palette__scrim and .ov-modal-bg both had). The accent
|
|
133
|
-
does not flip, and a pressable thing warming under the pointer is the world's
|
|
134
|
-
own rule for what the accent is for. */
|
|
135
|
-
.sky-body:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
|
|
136
|
-
.sky-body:focus-visible { outline: 2px solid var(--focus); outline-offset: 0; }
|
|
137
|
-
.sky-body[data-picked="1"] { box-shadow: inset 0 0 0 var(--line) var(--accent); }
|
|
138
|
-
|
|
139
|
-
/* ---- the five objects ----------------------------------------------------
|
|
140
|
-
THE MARK RESTATES THE WORD IN SHAPE. Every object differs from the others by
|
|
141
|
-
geometry — filled, haloed, orbited, hollow, hairline-edged — and not only by
|
|
142
|
-
tone, because a mark distinguished by colour alone tells a colour-blind
|
|
143
|
-
reader nothing. Same rule, same five shapes, as thinking.css.
|
|
144
|
-
|
|
145
|
-
`--lit` is the server's brightness. It rides the star's opacity and the
|
|
146
|
-
star's ALONE: the other four have no live share to show and dimming them
|
|
147
|
-
would invent one. A star whose brightness could not be READ arrives with
|
|
148
|
-
`--lit: 1` and says so in the panel's words. The 0.35 floor is a contrast
|
|
149
|
-
floor — below it the mark stops clearing 3:1 and a dim star becomes an absent
|
|
150
|
-
one, which is the wrong reading entirely. A star is never nothing. */
|
|
151
|
-
.sky-glyph {
|
|
152
|
-
display: block;
|
|
153
|
-
flex: none;
|
|
154
|
-
box-sizing: border-box;
|
|
155
|
-
width: var(--d, 10px);
|
|
156
|
-
height: var(--d, 10px);
|
|
157
|
-
border-radius: 50%;
|
|
158
|
-
background: var(--ink-faint);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/* star — filled, accent, dimmed by its live share, sized by what it caused. */
|
|
162
|
-
.sky-glyph[data-object="star"] {
|
|
163
|
-
background: var(--accent);
|
|
164
|
-
opacity: max(0.35, var(--lit, 1));
|
|
165
|
-
}
|
|
166
|
-
/* protostar — the halo: matter gathering, not yet burning. */
|
|
167
|
-
.sky-glyph[data-object="protostar"] {
|
|
168
|
-
background: var(--ink-soft);
|
|
169
|
-
box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 18%, transparent);
|
|
170
|
-
}
|
|
171
|
-
/* satellite — a small body with an orbit detached from it, so it reads as
|
|
172
|
-
something going round rather than as a thick edge. It is drawn beside the
|
|
173
|
-
thought it was folded into (sky.js `group`), because "it orbits that one now"
|
|
174
|
-
is a claim about position that the mark alone cannot make. */
|
|
175
|
-
.sky-glyph[data-object="satellite"] {
|
|
176
|
-
background: var(--ink-soft);
|
|
177
|
-
/* `--ink-faint` and not the room's `--rule`: there the orbit sits against a
|
|
178
|
-
card at 6px across, here it is the thing that tells a reader this body
|
|
179
|
-
belongs to the one beside it, and at the hairline's weight on `--surface-2`
|
|
180
|
-
`--rule` measured under the 3:1 non-text floor. */
|
|
181
|
-
outline: var(--line) solid var(--ink-faint);
|
|
182
|
-
outline-offset: 4px;
|
|
183
|
-
}
|
|
184
|
-
/* remnant — the hollow ring, and the whole reason size and brightness are two
|
|
185
|
-
channels: a thought that caused a great deal of work the project then tore
|
|
186
|
-
out keeps every pixel of its diameter and loses all of its fill. LARGE AND
|
|
187
|
-
DEAD, never small. `--ink-faint` and not `--rule` for the 3:1 floor. */
|
|
188
|
-
.sky-glyph[data-object="remnant"] {
|
|
189
|
-
background: transparent;
|
|
190
|
-
box-shadow: inset 0 0 0 1.5px var(--ink-faint);
|
|
191
|
-
}
|
|
192
|
-
/* cold body — a dark point with a hairline edge. It never ignited, so it has no
|
|
193
|
-
size at all, and the mark says that by being the one object with nothing
|
|
194
|
-
inside it and nothing around it. */
|
|
195
|
-
.sky-glyph[data-object="cold-body"] {
|
|
196
|
-
background: var(--surface);
|
|
197
|
-
box-shadow: inset 0 0 0 var(--line) var(--ink-faint);
|
|
198
|
-
}
|
|
199
|
-
/* A size that could not be READ is drawn at the floor diameter and marked, so
|
|
200
|
-
the floor is never read as a measured zero. Dotted, the sheet's existing
|
|
201
|
-
"this is a footnote, not a warning" weight. */
|
|
202
|
-
.sky-glyph[data-size="unknown"] { outline: var(--line) dotted var(--rule); outline-offset: 3px; }
|
|
203
|
-
|
|
204
|
-
/* ---- the panel ----------------------------------------------------------- */
|
|
205
|
-
.sky-panel {
|
|
206
|
-
border-left: var(--line) solid var(--rule-soft);
|
|
207
|
-
padding-left: var(--sp-md);
|
|
208
|
-
min-width: 0;
|
|
209
|
-
}
|
|
210
|
-
.sky-panel__head { display: flex; align-items: center; gap: 12px; min-height: 30px; }
|
|
211
|
-
.sky-panel__object {
|
|
212
|
-
font-family: var(--label-face);
|
|
213
|
-
font-size: var(--label-size);
|
|
214
|
-
letter-spacing: var(--label-space);
|
|
215
|
-
text-transform: var(--label-case);
|
|
216
|
-
color: var(--ink-soft);
|
|
217
|
-
}
|
|
218
|
-
.sky-panel__title { margin: var(--sp-hair) 0 var(--sp-sm); font-size: 18px; line-height: 1.3; }
|
|
219
|
-
.sky-panel__title a {
|
|
220
|
-
display: inline-block;
|
|
221
|
-
min-height: 24px;
|
|
222
|
-
padding: 2px 0;
|
|
223
|
-
color: var(--ink);
|
|
224
|
-
border-bottom: var(--line) solid var(--rule);
|
|
225
|
-
}
|
|
226
|
-
.sky-panel__title a:hover { border-bottom-color: var(--ink); text-decoration: none; }
|
|
227
|
-
.sky-panel__pos { margin: var(--sp-sm) 0 0; font-size: 13px; color: var(--accent-ink); }
|
|
228
|
-
.sky-panel__blurb { margin: var(--sp-hair) 0 var(--sp-md); font-size: 13px; color: var(--ink-faint); }
|
|
229
|
-
.sky-panel__out { margin: 0; }
|
|
230
|
-
.sky-pick { margin: 0; font-size: 13.5px; color: var(--ink-faint); max-width: var(--measure); }
|
|
231
|
-
/* The shell's prose-link recipe, restated for this page's two prose voices
|
|
232
|
-
rather than added to `style.css`'s selector list: that sheet is linked with a
|
|
233
|
-
hand-written `?v=` on all 29 hall pages, so a one-selector addition there is a
|
|
234
|
-
29-file cache bump for a rule only this page needs. Measured at 81x18 by the
|
|
235
|
-
kit before this existed — a link in a line with no vertical rhythm of its own
|
|
236
|
-
renders at its line box and fails the 24px floor on the height; the padding
|
|
237
|
-
buys it back and the equal negative margin hands the space to the layout so
|
|
238
|
-
the sentence does not re-space. */
|
|
239
|
-
.sky-pick a,
|
|
240
|
-
.sky-empty a {
|
|
241
|
-
display: inline-block;
|
|
242
|
-
min-width: 24px;
|
|
243
|
-
min-height: 24px;
|
|
244
|
-
padding: 3px 4px;
|
|
245
|
-
margin: 0 -4px;
|
|
246
|
-
line-height: 18px;
|
|
247
|
-
text-align: center;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
.sky-facts { display: grid; }
|
|
251
|
-
.sky-fact {
|
|
252
|
-
display: flex;
|
|
253
|
-
align-items: baseline;
|
|
254
|
-
gap: 14px;
|
|
255
|
-
padding: 10px 0;
|
|
256
|
-
border-top: var(--line) solid var(--rule-soft);
|
|
257
|
-
}
|
|
258
|
-
.sky-fact:last-child { border-bottom: var(--line) solid var(--rule-soft); }
|
|
259
|
-
.sky-fact__k {
|
|
260
|
-
flex: none;
|
|
261
|
-
font-family: var(--label-face);
|
|
262
|
-
font-size: var(--label-size);
|
|
263
|
-
letter-spacing: var(--label-space);
|
|
264
|
-
text-transform: var(--label-case);
|
|
265
|
-
color: var(--ink-faint);
|
|
266
|
-
}
|
|
267
|
-
.sky-fact__v {
|
|
268
|
-
margin-left: auto;
|
|
269
|
-
text-align: right;
|
|
270
|
-
font-size: 13.5px;
|
|
271
|
-
color: var(--ink-soft);
|
|
272
|
-
min-width: 0;
|
|
273
|
-
display: flex;
|
|
274
|
-
align-items: center;
|
|
275
|
-
justify-content: flex-end;
|
|
276
|
-
flex-wrap: wrap;
|
|
277
|
-
gap: 8px;
|
|
278
|
-
}
|
|
279
|
-
.sky-fact__v a {
|
|
280
|
-
display: inline-block;
|
|
281
|
-
min-height: 24px;
|
|
282
|
-
padding: 2px 0;
|
|
283
|
-
color: var(--ink-soft);
|
|
284
|
-
border-bottom: var(--line) solid var(--rule-soft);
|
|
285
|
-
}
|
|
286
|
-
.sky-fact__v a:hover { color: var(--ink); border-bottom-color: var(--ink); text-decoration: none; }
|
|
287
|
-
.sky-quiet { color: var(--ink-faint); }
|
|
288
|
-
.sky-brightword { font-size: 13px; color: var(--ink-soft); }
|
|
289
|
-
/* Bar and word together, never the bar alone — and no bar at all when the share
|
|
290
|
-
could not be read (sky.js brightHtml), because a bar at zero and a bar nobody
|
|
291
|
-
could measure look identical and one of them is a claim. */
|
|
292
|
-
.sky-bar {
|
|
293
|
-
display: block;
|
|
294
|
-
flex: none;
|
|
295
|
-
width: 56px;
|
|
296
|
-
height: 3px;
|
|
297
|
-
border-radius: 999px;
|
|
298
|
-
background:
|
|
299
|
-
linear-gradient(to right, var(--fill, var(--accent)) 0 var(--w, 0%), transparent var(--w, 0%) 100%),
|
|
300
|
-
var(--rule-soft);
|
|
301
|
-
}
|
|
302
|
-
.sky-bar[data-lit="out"] { --fill: var(--ink-faint); }
|
|
303
|
-
.sky-hist {
|
|
304
|
-
font-size: 11.5px;
|
|
305
|
-
color: var(--ink-faint);
|
|
306
|
-
text-decoration: none;
|
|
307
|
-
border-bottom: var(--line) dotted var(--rule);
|
|
308
|
-
cursor: help;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/* ---- the legend ---------------------------------------------------------- */
|
|
312
|
-
.sky-legend {
|
|
313
|
-
display: grid;
|
|
314
|
-
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
|
315
|
-
gap: var(--sp-sm) var(--sp-md);
|
|
316
|
-
margin: var(--sp-md) 0 0;
|
|
317
|
-
padding-top: var(--sp-md);
|
|
318
|
-
border-top: var(--line) solid var(--rule-soft);
|
|
319
|
-
}
|
|
320
|
-
.sky-leg { display: flex; align-items: flex-start; gap: 11px; }
|
|
321
|
-
.sky-leg .sky-glyph { margin-top: 4px; }
|
|
322
|
-
.sky-leg__body { display: grid; gap: 4px; min-width: 0; }
|
|
323
|
-
.sky-leg__name {
|
|
324
|
-
font-family: var(--label-face);
|
|
325
|
-
font-size: var(--label-size);
|
|
326
|
-
letter-spacing: var(--label-space);
|
|
327
|
-
text-transform: var(--label-case);
|
|
328
|
-
color: var(--ink-soft);
|
|
329
|
-
}
|
|
330
|
-
.sky-leg__what { font-size: 12.5px; line-height: 1.4; color: var(--ink-faint); }
|
|
331
|
-
|
|
332
|
-
/* ---- notes + the empty state --------------------------------------------- */
|
|
333
|
-
.sky-empty {
|
|
334
|
-
margin: 0;
|
|
335
|
-
padding: var(--sp-md) 0;
|
|
336
|
-
font-size: 14px;
|
|
337
|
-
color: var(--ink-soft);
|
|
338
|
-
max-width: var(--measure);
|
|
339
|
-
}
|
|
340
|
-
.sky-foot {
|
|
341
|
-
margin: var(--sp-md) 0 0;
|
|
342
|
-
font-size: 13px;
|
|
343
|
-
color: var(--ink-faint);
|
|
344
|
-
max-width: var(--measure);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/* ---- the phone -----------------------------------------------------------
|
|
348
|
-
The panel goes under the field and loses its rule-and-indent, because a
|
|
349
|
-
320px column has no width to give to a 22px gutter. Nothing below widens the
|
|
350
|
-
document: the page never scrolls sideways. */
|
|
351
|
-
@media (max-width: 860px) {
|
|
352
|
-
.sky-wrap { grid-template-columns: minmax(0, 1fr); }
|
|
353
|
-
.sky-panel {
|
|
354
|
-
border-left: 0;
|
|
355
|
-
padding-left: 0;
|
|
356
|
-
border-top: var(--line) solid var(--rule-soft);
|
|
357
|
-
padding-top: var(--sp-md);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
@media (max-width: 640px) {
|
|
361
|
-
/* One well to a row, and capped so a single well does not become a
|
|
362
|
-
full-width circle taller than the viewport. */
|
|
363
|
-
.sky-field-grid { grid-template-columns: minmax(0, 1fr); }
|
|
364
|
-
.sky-field { max-width: 300px; }
|
|
365
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>Your sky — {{worldName}}</title>
|
|
7
|
-
<meta name="description" content="The ambient view of your own thinking: goals as gravity wells, criteria as the rings inside them, and every thought you filed drawn at the size of the work it caused.">
|
|
8
|
-
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
9
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
12
|
-
<link rel="stylesheet" href="/builders/sky.css">
|
|
13
|
-
</head>
|
|
14
|
-
<!-- THE SKY (task 1004105 / BV2.R26). It is the ambient half of the ideator
|
|
15
|
-
surface and, by spec decision 11, NEVER the only path to anything it shows:
|
|
16
|
-
it takes no nav slot of its own and is reached from the room, which is the
|
|
17
|
-
page it is ambient ABOUT. That is deliberate rather than a saving — a third
|
|
18
|
-
ideator entry in the nav beside Ideas and Your thinking would make the
|
|
19
|
-
picture look load-bearing, and the direction's first refusal is that it is
|
|
20
|
-
not. The room links here; every thought here links back to its record. -->
|
|
21
|
-
<body data-page="sky" data-page-title="Your sky">
|
|
22
|
-
|
|
23
|
-
<div class="app">
|
|
24
|
-
<aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
|
|
25
|
-
<div class="app-scrim" id="app-scrim" hidden></div>
|
|
26
|
-
<div class="app-body">
|
|
27
|
-
<header class="app-topbar" id="app-topbar"></header>
|
|
28
|
-
<div class="app-content">
|
|
29
|
-
<!-- The LABEL voice: a working surface takes the board's head, not the
|
|
30
|
-
reading room's (modules/hall-ui/CLAUDE.md, the two page-head voices).
|
|
31
|
-
The sky is a working surface even though it is the ambient one. -->
|
|
32
|
-
<div class="page-head page-head--board">
|
|
33
|
-
<h1 class="page-head__h">Your sky</h1>
|
|
34
|
-
<p class="page-head__sub" id="sky-sub">Drawing what you filed…</p>
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
<main id="hall-main">
|
|
38
|
-
<section class="scroll" id="sky-scroll" aria-labelledby="sky-h">
|
|
39
|
-
<h2 id="sky-h" class="scroll__h">Your thinking, as a map</h2>
|
|
40
|
-
<p class="scroll__lede">
|
|
41
|
-
Open goals are gravity wells and their criteria are the rings inside them,
|
|
42
|
-
closing as each is met. A thought sits in the well that caught it, drawn at
|
|
43
|
-
the size of the work it caused, lit by how much of that work is still alive.
|
|
44
|
-
Nothing here is only here — the same trail is a list in
|
|
45
|
-
<a href="/builders/thinking">your thinking</a>.
|
|
46
|
-
</p>
|
|
47
|
-
|
|
48
|
-
<div class="sky-wrap">
|
|
49
|
-
<!-- The field and the panel are both written by sky.js from ONE
|
|
50
|
-
read, so nothing on this page can be a second copy of a number
|
|
51
|
-
the server already computed. -->
|
|
52
|
-
<div id="sky-map" class="sky-field-grid"></div>
|
|
53
|
-
<aside id="sky-panel" class="sky-panel" role="status" aria-live="polite" aria-label="The picked thought"></aside>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<div id="sky-legend" class="sky-legend"></div>
|
|
57
|
-
<p class="sky-foot" id="sky-foot" hidden></p>
|
|
58
|
-
</section>
|
|
59
|
-
</main>
|
|
60
|
-
|
|
61
|
-
<footer class="app-foot" id="app-foot"></footer>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
67
|
-
|
|
68
|
-
<script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
|
|
69
|
-
<script src="/builders/palette.js" defer></script>
|
|
70
|
-
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
71
|
-
<!-- The SAME shared vocabulary the room loads, at the same version: the server
|
|
72
|
-
says which of the five objects a thought is, this file says what the object
|
|
73
|
-
is called and what sentence goes with it, and two surfaces reading two
|
|
74
|
-
copies is exactly how the sky ends up calling a thought "burned out" while
|
|
75
|
-
the room beside it says "declined". -->
|
|
76
|
-
<script src="/builders/idea-objects.js?v=2026-09-20-r03"></script>
|
|
77
|
-
<script src="/builders/sky.js?v=2026-09-20-r26"></script>
|
|
78
|
-
</body>
|
|
79
|
-
</html>
|