@justai/cuts 0.78.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/Toolbar.astro +75 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justai/cuts",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
4
4
  "description": "A persona's named parts — the page shell that holds them, and the cuts themselves.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
package/src/Toolbar.astro CHANGED
@@ -61,6 +61,10 @@ interface Props {
61
61
  // destination, and justai.pro is above it (sending the parent "home" to a child platform is
62
62
  // the wrong direction). Every other persona keeps it.
63
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;
64
68
  // What the title strip says when the Kernel or the Posts cut is the one at the toolbar line.
65
69
  //
66
70
  // OMIT THEM AND NOTHING MOVES: the strip keeps naming the persona and its handle, which is right
@@ -93,9 +97,47 @@ interface Props {
93
97
  // The `profileIcon` slot still wins when a persona passes one — an inline SVG can take
94
98
  // `currentColor` where an <img> cannot, which is why qr passes its mark rather than pointing at it.
95
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 };
96
132
  }
97
133
  import BackToZone from "./BackToZone.astro";
98
- const { sectionsLabel, profileLabel, kernelLabel, postsLabel, home = true, files = true, filesLabel = "Files", kernelTitle, postsTitle, avatar = "/icon.svg" } = Astro.props;
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;
99
141
  import AuthMount from "./AuthMount.astro";
100
142
  ---
101
143
 
@@ -107,6 +149,15 @@ import AuthMount from "./AuthMount.astro";
107
149
  <div class="tb-left">
108
150
  {home && <BackToZone />}
109
151
  <nav class="tb-nav-group" aria-label={sectionsLabel}>
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
+ )}
110
161
  {/* THE PROFILE ICON IS THE PERSONA'S OWN FACE. Always — the founder's rule, and app.zone and
111
162
  qr had already settled it while thirteen siblings still showed a generic bust.
112
163
 
@@ -119,8 +170,24 @@ import AuthMount from "./AuthMount.astro";
119
170
  `tb-mark` is set whichever way the face arrives, because it is what the sizing rule looks
120
171
  for: a mark runs to the edge of its viewBox and a glyph does not, so they need different
121
172
  boxes to put the same ink on screen. */}
122
- <a class="tb-nav tb-mark" id="navProfile" href="#profile" aria-label={profileLabel} title={profileLabel}>
123
- {Astro.slots.has("profileIcon") ? <slot name="profileIcon" /> : <img src={avatar} alt="" width="19" height="19" loading="lazy" decoding="async" />}
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" />)}
124
191
  </a>
125
192
  {/* THE KERNEL IS ALWAYS THE BOLT. The founder's rule, and it is the shell's to draw rather
126
193
  than the persona's to remember.
@@ -137,9 +204,11 @@ import AuthMount from "./AuthMount.astro";
137
204
  <a class="tb-nav" id="navKernel" href="#kernel" aria-label={kernelLabel} title={kernelLabel}>
138
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>
139
206
  </a>
140
- <a class="tb-nav" id="navPosts" href="#posts" aria-label={postsLabel} title={postsLabel}>
141
- <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>
142
- </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
+ )}
143
212
  <slot name="navExtra" />
144
213
  </nav>
145
214
  {files && (