@justai/cuts 0.77.0 → 0.79.0
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/package.json +1 -1
- package/src/Toolbar.astro +130 -31
package/package.json
CHANGED
package/src/Toolbar.astro
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
//
|
|
9
9
|
// <Toolbar sectionsLabel={t.chromeSections} profileLabel={t.chromeProfile}
|
|
10
10
|
// kernelLabel={t.chromeApp} postsLabel={t.chromePosts}>
|
|
11
|
-
// <svg slot="kernelIcon" .../>
|
|
12
11
|
// <LangPicker slot="langPicker" current={lang} locales={locales} ... />
|
|
13
12
|
// </Toolbar>
|
|
14
13
|
//
|
|
@@ -16,11 +15,23 @@
|
|
|
16
15
|
// data-coupled ones are slots):
|
|
17
16
|
// • BackToZone — takes no props → internalised here (the persona no longer imports it).
|
|
18
17
|
// • the three nav aria-labels — resolved strings (like every other cut: Posts, LangPicker) → props.
|
|
19
|
-
// •
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
18
|
+
// • THE FOUR POSITIONS SAY FOUR DIFFERENT KINDS OF THING, and that is the whole design of the row:
|
|
19
|
+
// home is where you came FROM, the mark is WHO, the bolt is WHAT IT DOES, the bubble is what it
|
|
20
|
+
// SAYS. Neither of the middle two is a per-persona drawing any more, and the reason is not
|
|
21
|
+
// tidiness — an icon that repeats the identity spends the row's only distinction on something the
|
|
22
|
+
// row has already said two places to its left, and then neither icon means anything.
|
|
23
|
+
// • kernelIcon — GONE. It was a per-persona glyph (⚡ / clock / globe …) in a slot with NO fallback,
|
|
24
|
+
// which is the worst shape a slot can have: omit it and the persona shipped a live, invisible tap
|
|
25
|
+
// target. It is always the bolt now, drawn here.
|
|
26
|
+
// The kernel is the only section whose CONTENT differs between personas, which is exactly why its
|
|
27
|
+
// LABEL must not — otherwise there is nothing stable to navigate by, and somebody moving from qr
|
|
28
|
+
// to lendcam has to re-learn the row. A bolt is a state ("something runs here"), not a picture of
|
|
29
|
+
// a subject; a clock is true for one persona and a lie in the next.
|
|
30
|
+
// • profileIcon — OPTIONAL, and the DEFAULT is now the persona's own face rather than a generic
|
|
31
|
+
// bust: `avatar` (default `/icon.svg`) is the ecosystem's one-face path, the same one persona.yml
|
|
32
|
+
// declares and the registry stores. The bust is gone rather than demoted — a fallback that renders
|
|
33
|
+
// something plausible is how thirteen of fifteen personas wore nobody's face for months with
|
|
34
|
+
// nothing red anywhere. Pass the slot when an inline SVG is wanted instead of an <img>.
|
|
24
35
|
// Note what a mark cannot do: an <img> ignores `currentColor`, so `.active`'s accent never reaches
|
|
25
36
|
// it. It is not replaced with anything: the title strip already closes on this cut and opens off
|
|
26
37
|
// it, and a mark is an identity rather than a UI state. Slot presence adds `.tb-mark`, which the
|
|
@@ -50,6 +61,10 @@ interface Props {
|
|
|
50
61
|
// destination, and justai.pro is above it (sending the parent "home" to a child platform is
|
|
51
62
|
// the wrong direction). Every other persona keeps it.
|
|
52
63
|
home?: boolean;
|
|
64
|
+
|
|
65
|
+
// Posts is required only where there IS a stream (doctrine 02 — an empty cut shipped to look
|
|
66
|
+
// complete is the buffet). A child persona has none, so `parent` turns this off by default.
|
|
67
|
+
posts?: boolean;
|
|
53
68
|
// What the title strip says when the Kernel or the Posts cut is the one at the toolbar line.
|
|
54
69
|
//
|
|
55
70
|
// OMIT THEM AND NOTHING MOVES: the strip keeps naming the persona and its handle, which is right
|
|
@@ -67,9 +82,62 @@ interface Props {
|
|
|
67
82
|
// thing — names or sections, not one of each — so the API says so out loud.
|
|
68
83
|
kernelTitle?: string;
|
|
69
84
|
postsTitle?: string;
|
|
85
|
+
|
|
86
|
+
// queries: toolbar shows a generic person icon · the profile icon is not my persona ·
|
|
87
|
+
// how do I put my own mark in the toolbar · avatar
|
|
88
|
+
//
|
|
89
|
+
// THE PERSONA'S OWN FACE, BY DEFAULT RATHER THAN BY DECISION.
|
|
90
|
+
//
|
|
91
|
+
// This used to be a slot with a generic bust behind it, so a persona showed its own mark only if
|
|
92
|
+
// somebody remembered to pass one — and thirteen of fifteen never did. But the shell already knows
|
|
93
|
+
// where a persona's face is: ONE icon, everywhere, at `/icon.svg`. `persona.yml` declares it, the
|
|
94
|
+
// registry stores it, the catalog draws it. There was nothing to decide; there was only something
|
|
95
|
+
// to look up.
|
|
96
|
+
//
|
|
97
|
+
// The `profileIcon` slot still wins when a persona passes one — an inline SVG can take
|
|
98
|
+
// `currentColor` where an <img> cannot, which is why qr passes its mark rather than pointing at it.
|
|
99
|
+
avatar?: string;
|
|
100
|
+
|
|
101
|
+
// queries: child persona toolbar · files me feed toolbar · the toolbar inside /files ·
|
|
102
|
+
// how does a child persona get back to its parent · parent icon in the toolbar
|
|
103
|
+
//
|
|
104
|
+
// ── THE CHILD PERSONA TOOLBAR ────────────────────────────────────────────────────────────────
|
|
105
|
+
//
|
|
106
|
+
// `me`, `files` and `feed` are sub-personas mounted BY THE EDGE on every persona's host — you meet
|
|
107
|
+
// them at `qr.app.zone/files`, wearing qr's face, in qr's origin. They are somebody else's
|
|
108
|
+
// codebase living at your address, and their row has to say so.
|
|
109
|
+
//
|
|
110
|
+
// Pass `parent` and the row becomes the child's:
|
|
111
|
+
//
|
|
112
|
+
// the PARENT's face → this child's own mark → the bolt
|
|
113
|
+
//
|
|
114
|
+
// which is the ordinary row's grammar, unchanged. Position one is still "where you came from" —
|
|
115
|
+
// for a persona that is app.zone, and for a child it is the persona above it. Position two is
|
|
116
|
+
// still WHO. Position three is still the way IN. Nothing new was invented; the same four kinds of
|
|
117
|
+
// thing are being said by a shorter row, and there is no app.zone or justai icon in it because
|
|
118
|
+
// neither is where a visitor to `qr.app.zone/files` came from.
|
|
119
|
+
//
|
|
120
|
+
// THE PARENT'S FACE COSTS NOTHING TO FIND. A child runs in the parent's origin, so `/icon.svg` —
|
|
121
|
+
// the same path this cut already uses for a persona's own face — resolves to the PARENT's mark
|
|
122
|
+
// when the page is a child's. One path, two meanings, both correct, and neither needs a registry
|
|
123
|
+
// lookup or a build-time value the edge would have to inject.
|
|
124
|
+
//
|
|
125
|
+
// A child has no Posts and no BackToZone: `parent` turns both off unless asked otherwise.
|
|
126
|
+
//
|
|
127
|
+
// `icon` is optional and almost always should be: `/icon.svg` is already the parent's face when the
|
|
128
|
+
// child runs in the parent's origin. It exists for the one child that does not have to guess —
|
|
129
|
+
// `feed` already reads the host persona's avatar from the registry, and an absolute URL it was
|
|
130
|
+
// given beats a relative one it inferred.
|
|
131
|
+
parent?: { href?: string; label?: string; icon?: string };
|
|
70
132
|
}
|
|
71
133
|
import BackToZone from "./BackToZone.astro";
|
|
72
|
-
const {
|
|
134
|
+
const {
|
|
135
|
+
sectionsLabel, profileLabel, kernelLabel, postsLabel,
|
|
136
|
+
files = true, filesLabel = "Files", kernelTitle, postsTitle, avatar = "/icon.svg", parent,
|
|
137
|
+
} = Astro.props;
|
|
138
|
+
// A child's row answers UP to its parent, not out to the zone, and has no stream of its own — so
|
|
139
|
+
// both default off the moment `parent` is given, rather than each consumer remembering to say so.
|
|
140
|
+
const { home = !parent, posts = !parent } = Astro.props;
|
|
73
141
|
import AuthMount from "./AuthMount.astro";
|
|
74
142
|
---
|
|
75
143
|
|
|
@@ -81,35 +149,66 @@ import AuthMount from "./AuthMount.astro";
|
|
|
81
149
|
<div class="tb-left">
|
|
82
150
|
{home && <BackToZone />}
|
|
83
151
|
<nav class="tb-nav-group" aria-label={sectionsLabel}>
|
|
84
|
-
{/*
|
|
85
|
-
|
|
86
|
-
|
|
152
|
+
{/* POSITION ONE OF A CHILD'S ROW: the persona this one is mounted inside. `/icon.svg` is the
|
|
153
|
+
parent's face here, because a child runs in the parent's origin — the same path, read from
|
|
154
|
+
a different address. `#kernel` rather than `/` so the way back lands on the parent's work
|
|
155
|
+
and not on its biography, which is where a person who came from there was standing. */}
|
|
156
|
+
{parent && (
|
|
157
|
+
<a class="tb-nav tb-mark" id="navParent" href={parent.href ?? "/#kernel"} aria-label={parent.label ?? "Home"} title={parent.label ?? "Home"}>
|
|
158
|
+
<img src={parent.icon ?? "/icon.svg"} alt="" width="19" height="19" loading="lazy" decoding="async" />
|
|
159
|
+
</a>
|
|
160
|
+
)}
|
|
161
|
+
{/* THE PROFILE ICON IS THE PERSONA'S OWN FACE. Always — the founder's rule, and app.zone and
|
|
162
|
+
qr had already settled it while thirteen siblings still showed a generic bust.
|
|
87
163
|
|
|
88
|
-
The
|
|
89
|
-
|
|
90
|
-
|
|
164
|
+
The bust is gone rather than demoted. A fallback that renders something plausible is how a
|
|
165
|
+
persona ships wearing nobody's face for months: nothing is broken, nothing is red, and the
|
|
166
|
+
only reader who notices is somebody comparing two personas side by side. `/icon.svg` is
|
|
167
|
+
not a guess — it is the ecosystem's one-face rule, the same path `persona.yml` declares
|
|
168
|
+
and the registry stores.
|
|
91
169
|
|
|
92
|
-
`tb-mark` is set
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
170
|
+
`tb-mark` is set whichever way the face arrives, because it is what the sizing rule looks
|
|
171
|
+
for: a mark runs to the edge of its viewBox and a glyph does not, so they need different
|
|
172
|
+
boxes to put the same ink on screen. */}
|
|
173
|
+
{/* `tb-mark` says "a solid MARK is in this box" — it is what the sizing rule looks for, and a
|
|
174
|
+
mark runs to the edge of its viewBox where a stroke glyph sits inside 75% of one. A
|
|
175
|
+
persona's face is always a mark, so a persona always carries the class.
|
|
176
|
+
|
|
177
|
+
A CHILD IS THE OTHER CASE, and it was measured rather than assumed: files passes a stroke
|
|
178
|
+
folder, and with `tb-mark` on it, it rendered at 19px beside a 25.33px bolt — one size
|
|
179
|
+
smaller, which is exactly the defect the 25.33 rule exists to remove. So in child mode the
|
|
180
|
+
slotted icon takes the GLYPH box and the row reads level. A child that draws a solid mark
|
|
181
|
+
instead should say so in its own CSS; every child in the fleet draws a glyph today. */}
|
|
182
|
+
<a class:list={["tb-nav", { "tb-mark": !parent }]} id="navProfile" href="#profile" aria-label={profileLabel} title={profileLabel}>
|
|
183
|
+
{/* A CHILD MUST PASS ITS OWN MARK. The default `/icon.svg` is the face of whoever owns this
|
|
184
|
+
origin — right for a persona, and its PARENT for a child, which would put the same
|
|
185
|
+
picture in two places and say nothing in either. So when `parent` is given there is no
|
|
186
|
+
fallback: a child that passes nothing renders an empty box, which is a defect somebody
|
|
187
|
+
sees, rather than a duplicate face, which is a defect nobody reports. */}
|
|
188
|
+
{Astro.slots.has("profileIcon")
|
|
189
|
+
? <slot name="profileIcon" />
|
|
190
|
+
: (!parent && <img src={avatar} alt="" width="19" height="19" loading="lazy" decoding="async" />)}
|
|
106
191
|
</a>
|
|
192
|
+
{/* THE KERNEL IS ALWAYS THE BOLT. The founder's rule, and it is the shell's to draw rather
|
|
193
|
+
than the persona's to remember.
|
|
194
|
+
|
|
195
|
+
It was a bare `<slot name="kernelIcon" />` with NO fallback, which is the worst shape a
|
|
196
|
+
slot can have: a persona that passed nothing shipped a live, invisible tap target where
|
|
197
|
+
every sibling showed a mark — no error, no warning, and visible only beside another
|
|
198
|
+
persona. Fourteen of fifteen passed their own drawing instead, so the row said something
|
|
199
|
+
different in every repo about the same thing.
|
|
200
|
+
|
|
201
|
+
The bolt says WHAT THE SECTION IS — the persona's one job, its live surface — and the
|
|
202
|
+
profile icon two places to the left already says WHO. An icon that repeats the identity
|
|
203
|
+
spends the row's only distinction on a thing the row has already said. */}
|
|
107
204
|
<a class="tb-nav" id="navKernel" href="#kernel" aria-label={kernelLabel} title={kernelLabel}>
|
|
108
|
-
<
|
|
109
|
-
</a>
|
|
110
|
-
<a class="tb-nav" id="navPosts" href="#posts" aria-label={postsLabel} title={postsLabel}>
|
|
111
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.3 9.6 9.6 0 0 1-4-.9L3 21l1.1-5A8.4 8.4 0 1 1 21 11.5z"/></svg>
|
|
205
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.9 3L5.7 13.35h5.4L10.2 21l8.1-10.8h-5.4l.9-7.2z"/></svg>
|
|
112
206
|
</a>
|
|
207
|
+
{posts && (
|
|
208
|
+
<a class="tb-nav" id="navPosts" href="#posts" aria-label={postsLabel} title={postsLabel}>
|
|
209
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.3 9.6 9.6 0 0 1-4-.9L3 21l1.1-5A8.4 8.4 0 1 1 21 11.5z"/></svg>
|
|
210
|
+
</a>
|
|
211
|
+
)}
|
|
113
212
|
<slot name="navExtra" />
|
|
114
213
|
</nav>
|
|
115
214
|
{files && (
|