@usableapp/cardds 0.7.89 → 0.7.90

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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  > For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
4
4
 
5
+ ## 0.7.90 — 2026-10-13
6
+
7
+ Nothing here breaks an existing screen: one component (`StickyHeading`) and four knobs are added and none removed, and a `StickyThread lapped` without a heading draws, leans and spaces every note as in 0.7.89 (computed style of all 455 stories already in the gallery identical before and after).
8
+
9
+ ### Added
10
+ - **`StickyHeading`: the heading of a chat told in notes, what it is about for as long as that lasts** (Lh 2026-10-13, ruled from two sketches side by side: the plain dotted heading kept, the strip of note paper lying askew rejected — no paper, no tone, no tilt, no variant). Before → after: a chat in notes had no way to say "this chat is about a job agreed, and when", short of a note of its own among the notes → `<StickyHeading title caption />`, a subject and a short caption written on whatever it stands on with a dotted rule under it, one line where there is room (the subject, then the caption), two where there is not, a title too long for its line cut with an ellipsis. It stands in two places that already existed and no other component has a new prop: in a `Sticky`'s `stuck` (the list: across the top of that chat's note on its own paper; the note's words start under it and show two lines at most, so they never run under the foot, and a stamp stuck on the note stays) and as the FIRST child of a `StickyThread` (the room: across the thread's top on the screen's ground, staying there while the notes run under it, with 16 before the first note). The app renders it for as long as the subject lasts and stops when it ends; the notes are as they were before it. Information only, nothing to press.
11
+ - **`StickyHeading as`: `span` (default) or `h2`.** In the room pass `as="h2"`: it is the heading of that thread, a sub-heading under the screen's h1 (the `TopBar`'s title, who the chat is with), heard as "subject caption, heading level 2". On a note keep the default: a note may be a button, and a button holds no heading, so there the words are part of the note's name (read after the note's words and its foot, because the layer that holds things stuck on a note comes after the paper).
12
+ - **Four knobs:** `--sticky-heading-title-font` (`--type-label`), `--sticky-heading-caption-font` (`--type-caption`), `--sticky-heading-gap` (between the subject and its caption, `--sp-2`) and `--sticky-heading-room` (the room a note keeps at the top of its paper for the heading, `--sp-12` + `--sp-4`: 64). Gallery: the page `StickyHeading` after `StickyThread` in `sticky chat`, stories `InARoom`, `OnANote`, `Ends`.
13
+
14
+ ### Changed
15
+ - **A thread's lean and its first post count posts only.** Before → after: `.sticky-thread--lapped > .sticky-post:nth-child(even)` turned the even children the other way and `.sticky-post:first-child` had no gap above it, so a heading as the first child of a lapped thread made every note lean the other way round and gave the first post a gap → `:nth-child(even of .sticky-post)` and `:nth-child(1 of .sticky-post)`: a thread with a heading leans exactly as the same thread without one (−2° · 2° in turn from its first note) and its first post stands 16 under the heading. A thread of posts alone is read the same as before.
16
+ - **Taking the heading out of a room lifts the notes by its room.** Before → after (stated, not a regression): none, a heading is new → the notes keep their size, their lean and their place against the first note, and the whole run rises by the heading's height and its 16 (they stand where they stood before the subject began). In the list nothing moves: the heading is laid over the note's paper and holds no room of the board's.
17
+
5
18
  ## 0.7.89 — 2026-10-13
6
19
 
7
20
  Nothing here breaks an existing screen: a prop pair (`Stamp arrive` + `onArrived`) is added and none removed, a `Stamp` without `arrive` renders the html it did, and a `StampOn arrive` is struck and tells the app exactly as before (the one rule that gave it the strike lists a second holder; no keyframe, knob or class is new but `stamp--arrive`).
package/README.md CHANGED
@@ -607,6 +607,12 @@ A sticky note is a square of paper with a few words on it. The family is in `src
607
607
  stand at the left and mine (`side="right"`) at the right, each turn lying over a corner of the one before
608
608
  (`--sticky-thread-lap` down, `--sticky-thread-lap-x` across), the time close under its note, the name only heard.
609
609
  Two turns from one side stand `--sticky-thread-same-gap` apart.
610
+ - **`StickyHeading`** — what a chat told in notes is about, for as long as that lasts: a `title` and a short `caption`
611
+ (when) written on what it stands on, a dotted rule under it; information only. It stands in two places and no other
612
+ component has a prop for it. In a `Sticky`'s `stuck` (the list) it lies across the top of that chat's note, and the
613
+ note's words start under it and show two lines at most. As the first child of a `StickyThread` (the room, with
614
+ `as="h2"`: the sub-heading under the screen's h1) it stays at the top while the notes run under it. The app stops
615
+ rendering it when the subject ends. Knobs: `--sticky-heading-title-font`, `-caption-font`, `-gap`, `-room`.
610
616
  - **`StickyAnswer`** — the note you answer on. Its child is a `Sticky flat` holding a `StickyField`. At rest it waits
611
617
  at the screen's foot near the right, three quarters of it in sight (`--sticky-answer-show`). A touch on it puts the
612
618
  cursor in the field and the holder asks to write: `writing` + `onWritingChange(writing, reason)`, or
package/css/sticky.css CHANGED
@@ -234,7 +234,7 @@
234
234
  padding-block-start: var(--sp-4); /* the first note's turned corner */
235
235
  }
236
236
  .sticky-thread--lapped > .sticky-post { position: relative; display: grid; grid-template-columns: auto; justify-self: start; justify-items: start; row-gap: var(--sticky-thread-time-gap, var(--sp-0)); margin-block-start: var(--sticky-thread-same-gap, var(--sp-2)); }
237
- .sticky-thread--lapped > .sticky-post:first-child { margin-block-start: 0; }
237
+ .sticky-thread--lapped > .sticky-post:nth-child(1 of .sticky-post) { margin-block-start: 0; }
238
238
  .sticky-thread--lapped > .sticky-post--right { justify-self: end; justify-items: end; }
239
239
  .sticky-thread--lapped > .sticky-post:not(.sticky-post--right) + .sticky-post--right,
240
240
  .sticky-thread--lapped > .sticky-post--right + .sticky-post:not(.sticky-post--right) { margin-block-start: calc(-1 * var(--sticky-thread-lap, calc(var(--sp-1) * 19))); } /* 76 */
@@ -244,9 +244,9 @@
244
244
  .sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__name { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } /* as .t-sr: heard, not seen */
245
245
  .sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__time { margin-inline: 0; }
246
246
  .sticky-thread--lapped > .sticky-post > .sticky-post__head:not(:has(> .sticky-post__time)) { display: none; }
247
- /* each note leans a little, one way then the other */
247
+ /* each note leans a little, one way then the other; a post counts among posts only, so a heading before the first changes no note's lean */
248
248
  .sticky-thread--lapped > .sticky-post > .sticky-post__notes > .sticky { rotate: calc(-1 * var(--sticky-thread-tilt, 2deg)); }
249
- .sticky-thread--lapped > .sticky-post:nth-child(even) > .sticky-post__notes > .sticky { rotate: var(--sticky-thread-tilt, 2deg); }
249
+ .sticky-thread--lapped > .sticky-post:nth-child(even of .sticky-post) > .sticky-post__notes > .sticky { rotate: var(--sticky-thread-tilt, 2deg); }
250
250
  /* under an answer note the thread ends with the room that note stands in: the last turn scrolls up clear of it */
251
251
  .screen:has(> .sticky-answer) .sticky-thread--lapped { padding-block-end: calc(var(--sticky-size, calc(var(--sp-12) * 3.5)) * var(--sticky-answer-show, 0.75) + var(--sp-6)); }
252
252
 
@@ -374,3 +374,31 @@
374
374
  .sticky-answer__tools > .sticky-pick { justify-content: start; }
375
375
  .sticky-answer__tray { display: grid; gap: var(--sticky-answer-tray-gap, var(--sp-2)); }
376
376
  @media (prefers-reduced-motion: reduce) { .sticky-answer__scrim, .sticky-answer__note, .sticky-answer__under { transition: none; } }
377
+
378
+ /* ---- sticky heading: what the notes are about — the subject and a short caption written on whatever it stands on, a dotted rule under it ----
379
+ Two places, both existing ones:
380
+ <div class="sticky__stuck"><span class="sticky-heading"><span class="sticky-heading__title">…</span><span class="sticky-heading__caption">…</span></span></div>
381
+ <div class="sticky-thread sticky-thread--lapped"><h2 class="sticky-heading">…</h2><div class="sticky-post">…</div>…</div> */
382
+ .sticky-heading {
383
+ display: flex;
384
+ flex-wrap: wrap;
385
+ align-items: baseline;
386
+ column-gap: var(--sticky-heading-gap, var(--sp-2));
387
+ box-sizing: border-box;
388
+ min-width: 0;
389
+ margin: 0;
390
+ padding-block-end: var(--sp-0);
391
+ border-block-end: var(--rule-w) dotted currentColor;
392
+ font: inherit;
393
+ }
394
+ .sticky-heading__title { min-width: 0; max-width: 100%; font: var(--sticky-heading-title-font, var(--type-label)); line-height: 1.35; /* Thai marks stack taller than the label's 1.2, and overflow clips them */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
395
+ .sticky-heading__caption { font: var(--sticky-heading-caption-font, var(--type-caption)); color: color-mix(in srgb, currentColor 62%, transparent); white-space: nowrap; }
396
+ /* on a note: across the top of its paper, and the note's words start under it */
397
+ .sticky__stuck > .sticky-heading { position: absolute; inset: var(--sp-2) var(--sp-4) auto; }
398
+ .sticky:has(> .sticky__stuck > .sticky-heading) > .sticky__paper { padding-block-start: var(--sticky-heading-room, calc(var(--sp-12) + var(--sp-4))); }
399
+ /* on a board the foot lies over the note's last line: under a heading the words are two lines, clear of it */
400
+ .sticky-board .sticky:has(> .sticky__stuck > .sticky-heading) .sticky__body { flex: none; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
401
+ /* in a thread: across its top on the screen's ground, staying there while the notes run under it; the room before the first note is the turned corner's, as the thread's own top */
402
+ /* the ground is drawn a little wider than the heading each way: a turned note's corner and its shadow stand outside the thread's width */
403
+ .sticky-thread > .sticky-heading { position: sticky; inset-block-start: 0; z-index: 2; padding-block-start: var(--sp-2); background: var(--surface); color: var(--on-surface); box-shadow: calc(-1 * var(--sp-3)) 0 0 var(--surface), var(--sp-3) 0 0 var(--surface); }
404
+ .sticky-thread--lapped > .sticky-heading { margin-block-end: var(--sp-4); }
package/dist/cardds.css CHANGED
@@ -4030,7 +4030,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
4030
4030
  padding-block-start: var(--sp-4); /* the first note's turned corner */
4031
4031
  }
4032
4032
  .sticky-thread--lapped > .sticky-post { position: relative; display: grid; grid-template-columns: auto; justify-self: start; justify-items: start; row-gap: var(--sticky-thread-time-gap, var(--sp-0)); margin-block-start: var(--sticky-thread-same-gap, var(--sp-2)); }
4033
- .sticky-thread--lapped > .sticky-post:first-child { margin-block-start: 0; }
4033
+ .sticky-thread--lapped > .sticky-post:nth-child(1 of .sticky-post) { margin-block-start: 0; }
4034
4034
  .sticky-thread--lapped > .sticky-post--right { justify-self: end; justify-items: end; }
4035
4035
  .sticky-thread--lapped > .sticky-post:not(.sticky-post--right) + .sticky-post--right,
4036
4036
  .sticky-thread--lapped > .sticky-post--right + .sticky-post:not(.sticky-post--right) { margin-block-start: calc(-1 * var(--sticky-thread-lap, calc(var(--sp-1) * 19))); } /* 76 */
@@ -4040,9 +4040,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
4040
4040
  .sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__name { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } /* as .t-sr: heard, not seen */
4041
4041
  .sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__time { margin-inline: 0; }
4042
4042
  .sticky-thread--lapped > .sticky-post > .sticky-post__head:not(:has(> .sticky-post__time)) { display: none; }
4043
- /* each note leans a little, one way then the other */
4043
+ /* each note leans a little, one way then the other; a post counts among posts only, so a heading before the first changes no note's lean */
4044
4044
  .sticky-thread--lapped > .sticky-post > .sticky-post__notes > .sticky { rotate: calc(-1 * var(--sticky-thread-tilt, 2deg)); }
4045
- .sticky-thread--lapped > .sticky-post:nth-child(even) > .sticky-post__notes > .sticky { rotate: var(--sticky-thread-tilt, 2deg); }
4045
+ .sticky-thread--lapped > .sticky-post:nth-child(even of .sticky-post) > .sticky-post__notes > .sticky { rotate: var(--sticky-thread-tilt, 2deg); }
4046
4046
  /* under an answer note the thread ends with the room that note stands in: the last turn scrolls up clear of it */
4047
4047
  .screen:has(> .sticky-answer) .sticky-thread--lapped { padding-block-end: calc(var(--sticky-size, calc(var(--sp-12) * 3.5)) * var(--sticky-answer-show, 0.75) + var(--sp-6)); }
4048
4048
 
@@ -4171,6 +4171,34 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
4171
4171
  .sticky-answer__tray { display: grid; gap: var(--sticky-answer-tray-gap, var(--sp-2)); }
4172
4172
  @media (prefers-reduced-motion: reduce) { .sticky-answer__scrim, .sticky-answer__note, .sticky-answer__under { transition: none; } }
4173
4173
 
4174
+ /* ---- sticky heading: what the notes are about — the subject and a short caption written on whatever it stands on, a dotted rule under it ----
4175
+ Two places, both existing ones:
4176
+ <div class="sticky__stuck"><span class="sticky-heading"><span class="sticky-heading__title">…</span><span class="sticky-heading__caption">…</span></span></div>
4177
+ <div class="sticky-thread sticky-thread--lapped"><h2 class="sticky-heading">…</h2><div class="sticky-post">…</div>…</div> */
4178
+ .sticky-heading {
4179
+ display: flex;
4180
+ flex-wrap: wrap;
4181
+ align-items: baseline;
4182
+ column-gap: var(--sticky-heading-gap, var(--sp-2));
4183
+ box-sizing: border-box;
4184
+ min-width: 0;
4185
+ margin: 0;
4186
+ padding-block-end: var(--sp-0);
4187
+ border-block-end: var(--rule-w) dotted currentColor;
4188
+ font: inherit;
4189
+ }
4190
+ .sticky-heading__title { min-width: 0; max-width: 100%; font: var(--sticky-heading-title-font, var(--type-label)); line-height: 1.35; /* Thai marks stack taller than the label's 1.2, and overflow clips them */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4191
+ .sticky-heading__caption { font: var(--sticky-heading-caption-font, var(--type-caption)); color: color-mix(in srgb, currentColor 62%, transparent); white-space: nowrap; }
4192
+ /* on a note: across the top of its paper, and the note's words start under it */
4193
+ .sticky__stuck > .sticky-heading { position: absolute; inset: var(--sp-2) var(--sp-4) auto; }
4194
+ .sticky:has(> .sticky__stuck > .sticky-heading) > .sticky__paper { padding-block-start: var(--sticky-heading-room, calc(var(--sp-12) + var(--sp-4))); }
4195
+ /* on a board the foot lies over the note's last line: under a heading the words are two lines, clear of it */
4196
+ .sticky-board .sticky:has(> .sticky__stuck > .sticky-heading) .sticky__body { flex: none; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
4197
+ /* in a thread: across its top on the screen's ground, staying there while the notes run under it; the room before the first note is the turned corner's, as the thread's own top */
4198
+ /* the ground is drawn a little wider than the heading each way: a turned note's corner and its shadow stand outside the thread's width */
4199
+ .sticky-thread > .sticky-heading { position: sticky; inset-block-start: 0; z-index: 2; padding-block-start: var(--sp-2); background: var(--surface); color: var(--on-surface); box-shadow: calc(-1 * var(--sp-3)) 0 0 var(--surface), var(--sp-3) 0 0 var(--surface); }
4200
+ .sticky-thread--lapped > .sticky-heading { margin-block-end: var(--sp-4); }
4201
+
4174
4202
  /* ---- css/stamp.css — stamp, empty place, album page, a stamp stuck on a surface ---- */
4175
4203
  /* cardds — stamps: the paper stamp, an empty place for one, the album page, a stamp stuck on a surface.
4176
4204
  A stamp is its own paper, like a sticky note: it brings its ground and its ink, whatever card or palette it lies on.