@justai/cuts 0.48.0 → 0.50.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/Posts.astro +24 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justai/cuts",
3
- "version": "0.48.0",
3
+ "version": "0.50.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/Posts.astro CHANGED
@@ -16,17 +16,29 @@
16
16
  // stay dark. Everything else is shared.
17
17
  //
18
18
  // THE SOCIAL LAYER is in here: a persona's feed carries replies from OTHER personas, and they must read
19
- // as themselves — their own icon (or glyph), their own name and @handle, and a link to their own
20
- // profile — not as the host persona. That rendering was invented on the two apexes (justai, app.zone)
21
- // and lived only there; folding it UP into this cut is what lets every persona show a cross-reply
22
- // correctly instead of mislabelling it as its own. The one asymmetry it needs: `handle` is the persona
23
- // KEY (the feed query and the is-this-mine test) while `displayHandle` is what is printed after the @
24
- // they differ exactly on the apexes (app.zone @appzone, justai → @justai.pro).
19
+ // as themselves — their own icon (or glyph), their own name, and a link to their own profile — not as the
20
+ // host persona. That rendering was invented on the two apexes (justai, app.zone) and lived only there;
21
+ // folding it UP into this cut is what lets every persona show a cross-reply correctly instead of
22
+ // mislabelling it as its own.
23
+ //
24
+ // EVERYONE IS NAMED ONCE no @handle anywhere in this feed.
25
+ //
26
+ // It started with the personas, where the name and the handle are the same word by construction: app.zone
27
+ // beside @appzone, AMRAP beside @amrap, Tabata beside @tabata. One reads as identity; six down a thread read
28
+ // as stutter, and on a 480px column each one spent width saying nothing new. The self-reply had always
29
+ // rendered as `name · time`, so dropping the rest just made the family read the way the host already did.
30
+ //
31
+ // Humans were kept for one release on the argument that a person's display name and their account are two
32
+ // different facts. Overruled, and the call is better than the argument was: what a reader needs from a line
33
+ // in a conversation is who said it, and the name is that. The @handle was a convention borrowed from a
34
+ // product where handles are how you address someone — nobody is addressed here.
35
+ //
36
+ // `handle` (the prop) and `authorHandle` (the API field) both stay: they are keys, not labels. The feed
37
+ // query, the is-this-mine test, and the link to another persona's profile all read them. Nothing prints them.
25
38
  interface Props {
26
39
  handle: string; // the persona KEY — the feed query (?persona=) and the "is this reply mine?" test
27
40
  name: string; // the persona's display name; rendered RAW, so a name may carry markup (justai's ".pro")
28
41
  footer: string; // the localized footer tagline after "justai.pro ·"
29
- displayHandle?: string; // the rendered @handle when it differs from the key (app.zone → @appzone)
30
42
  avatarBg?: string; // post-avatar tile background (default #fff)
31
43
  avatarBgLight?: string; // light-scheme override for that tile (a dark gradient must invert, not stay dark)
32
44
  // Which file is THIS persona's face in the feed. Defaults to /icon.svg, which every persona serves at its
@@ -49,7 +61,7 @@ interface Props {
49
61
  // the ground. The page's own media query is reliable, so the page picks the file and the file stays dumb.
50
62
  avatarSrcDark?: string;
51
63
  }
52
- const { handle, name, footer, displayHandle, avatarBg, avatarBgLight, avatarSrc, avatarBare, avatarSrcDark } = Astro.props;
64
+ const { handle, name, footer, avatarBg, avatarBgLight, avatarSrc, avatarBare, avatarSrcDark } = Astro.props;
53
65
  const vars = [
54
66
  avatarBg && `--feed-av-bg:${avatarBg}`,
55
67
  avatarBgLight && `--feed-av-bg-light:${avatarBgLight}`,
@@ -60,7 +72,6 @@ const vars = [
60
72
  class="cut posts"
61
73
  id="posts"
62
74
  data-persona={handle}
63
- data-handle={displayHandle ?? handle}
64
75
  data-name={name}
65
76
  data-av={avatarSrc ?? "/icon.svg"}
66
77
  data-av-dark={avatarSrcDark}
@@ -121,7 +132,7 @@ const vars = [
121
132
 
122
133
  .ph { display: flex; align-items: center; gap: 5px; font-size: var(--fs-xs); flex-wrap: wrap; }
123
134
  .ph b { font-weight: 600; }
124
- .phandle, .dot, .pt { color: var(--text-dim); font-weight: 400; }
135
+ .dot, .pt { color: var(--text-dim); font-weight: 400; }
125
136
  /* a persona's name in the feed links to its profile (X-style); humans have no link */
126
137
  .pn { color: inherit; text-decoration: none; }
127
138
  .pn:hover b { color: var(--accent); }
@@ -164,7 +175,6 @@ const vars = [
164
175
  // persona's OWN name and is injected RAW — it is authored in the persona's source, never user input,
165
176
  // so it may carry markup (justai's ".pro" span). Every name that comes from the API is escaped.
166
177
  const PERSONA = section.dataset.persona || "";
167
- const HANDLE = section.dataset.handle || PERSONA;
168
178
  const NAME = section.dataset.name || "";
169
179
  if (!PERSONA) return;
170
180
  // Astro scopes this component's CSS by a data-astro-cid-* attribute; runtime nodes lack it, so copy it.
@@ -236,11 +246,11 @@ const vars = [
236
246
  : `<span style="font-size:16px">${esc(c.authorGlyph || "✦")}</span>`;
237
247
  const nm = own ? NAME : esc(c.authorName); // own name is authored markup; an API name is escaped
238
248
  const purl = own ? "#profile" : `${c.personaUrl || homeOf(c.authorHandle)}/#profile`;
239
- const meta = own ? `· ${timeAgo(c.createdAt)}` : `@${esc(c.authorHandle)} · ${timeAgo(c.createdAt)}`;
249
+ const meta = `· ${timeAgo(c.createdAt)}`; // a persona's name IS its handle — see the header note
240
250
  return `<div class="trow${last ? " last" : ""}"><div class="avc"><span class="rav">${av}</span>${ln}</div><div class="cc"><div class="ch"><a class="pn" href="${esc(purl)}"><b>${nm}</b></a>${ck}<span class="muted">${meta}</span></div><p class="cb">${esc(c.body)}</p></div></div>`;
241
251
  }
242
252
  const initial = esc(String(c.authorName || "?").slice(0, 1));
243
- return `<div class="trow${last ? " last" : ""}"><div class="avc"><span class="uav" style="--c:${esc(c.authorColor || "#888")}">${initial}</span>${ln}</div><div class="cc"><div class="ch"><b>${esc(c.authorName)}</b><span class="muted">@${esc(c.authorHandle)} · ${timeAgo(c.createdAt)}</span></div><p class="cb">${esc(c.body)}</p></div></div>`;
253
+ return `<div class="trow${last ? " last" : ""}"><div class="avc"><span class="uav" style="--c:${esc(c.authorColor || "#888")}">${initial}</span>${ln}</div><div class="cc"><div class="ch"><b>${esc(c.authorName)}</b><span class="muted" ${timeAgo(c.createdAt)}</span></div><p class="cb">${esc(c.body)}</p></div></div>`;
244
254
  };
245
255
  const post = (p: P): string => {
246
256
  const cs = p.comments || [];
@@ -250,7 +260,7 @@ const vars = [
250
260
  const n = String(p.id || "").split(":").pop() || "";
251
261
  const perma = n ? `/feed/${n}` : "";
252
262
  const pt = perma ? `<a class="pt" href="${perma}">${timeAgo(p.createdAt)}</a>` : `<span class="pt">${timeAgo(p.createdAt)}</span>`;
253
- return `<article class="post"${perma ? ` data-href="${perma}"` : ""}><div class="trow"><div class="avc"><span class="pa">${AV}</span>${cs.length ? '<span class="ln"></span>' : ""}</div><div class="cc"><div class="ph"><a class="pn" href="#profile"><b>${NAME}</b></a><span class="phandle">@${esc(HANDLE)}</span><span class="dot">·</span>${pt}</div><p class="pbd">${esc(p.body)}</p>${pact}</div></div>${inner}</article>`;
263
+ return `<article class="post"${perma ? ` data-href="${perma}"` : ""}><div class="trow"><div class="avc"><span class="pa">${AV}</span>${cs.length ? '<span class="ln"></span>' : ""}</div><div class="cc"><div class="ph"><a class="pn" href="#profile"><b>${NAME}</b></a><span class="dot">·</span>${pt}</div><p class="pbd">${esc(p.body)}</p>${pact}</div></div>${inner}</article>`;
254
264
  };
255
265
 
256
266
  let done = false;