@poodle64/ui 2026.10.2 → 2026.10.4

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/README.md CHANGED
@@ -911,9 +911,11 @@ Settled here, the same in every module, and not props:
911
911
  above its card; figures in the body face with tabular numerals, money out
912
912
  bracketed and money in green (`formatCurrency(v, { negative: 'brackets' })`).
913
913
  - **Sorting and the groups' one control.** Click a column head to sort by it,
914
- largest or newest first; click again to reverse. A sorted list is one flat
915
- run with no group heads and shows the Date column even by day. Until the
916
- viewer sorts, rows sit newest first in their groups. A chevron in the head's
914
+ largest or newest first; click again to reverse; a third click returns to the
915
+ default order. A sort orders rows inside the grouping the viewer chose (the
916
+ groups stay newest first); grouped by none, the list is one flat run. The
917
+ head shows its state: a down chevron descending, an up chevron ascending,
918
+ none at rest. Until the viewer sorts, rows sit newest first. A chevron in the head's
917
919
  gutter opens or closes every group at once; where rows tick, the gutter
918
920
  widens to hold it beside the select-all checkbox.
919
921
  - **The Columns menu** holds every column as a tick, the three fixed ones
@@ -26,6 +26,9 @@
26
26
  // not sit in the situation→component registry either.
27
27
  // A page control that shares its corner sets `--ds-report-clearance` on the
28
28
  // document to lift the trigger clear of it (the librarian composer does).
29
+ // Every other page gets the trigger's footprint, net of that lift, as
30
+ // `--ds-report-reserve`: the shell ends its padded content with that much
31
+ // room, so a control under the corner can always scroll clear.
29
32
 
30
33
  let {
31
34
  /** Where the report POSTs. The app's own route, defaulting to the one every household app serves. */
@@ -50,6 +53,15 @@
50
53
  const messageId = `feedback-message-${uid}`;
51
54
  const screenshotId = `feedback-screenshot-${uid}`;
52
55
 
56
+ // The trigger's top edge sits 3.75rem up (1rem offset, 2.75rem disc); 4.5rem
57
+ // leaves a gap above it. Resolved on the document, so a lift set or cleared
58
+ // later moves it too.
59
+ $effect(() => {
60
+ const root = document.documentElement;
61
+ root.style.setProperty('--ds-report-reserve', 'calc(4.5rem - var(--ds-report-clearance, 0px))');
62
+ return () => root.style.removeProperty('--ds-report-reserve');
63
+ });
64
+
53
65
  async function submit() {
54
66
  phase = 'submitting';
55
67
 
@@ -720,8 +720,13 @@
720
720
  {/if}
721
721
 
722
722
  <div class="flex min-h-0 min-w-0 flex-1 flex-col">
723
+ <!-- The bar wraps when crowded, at every width, and pads top and bottom
724
+ alike so a second row clears both edges. Kept to one row, it shrank
725
+ `context` below its own content and the next item painted over it, so
726
+ the app's own control under the overlap could not be clicked
727
+ (mission-command's workspace menu, 04/10/2026). -->
723
728
  <header
724
- class="ds-shell-bar bg-shell/80 text-shell-foreground border-border sticky top-0 z-20 flex min-h-14 flex-none flex-wrap items-center gap-2 border-b px-3 pt-[env(safe-area-inset-top)] backdrop-blur sm:gap-3 sm:px-5 xl:flex-nowrap"
729
+ class="ds-shell-bar bg-shell/80 text-shell-foreground border-border sticky top-0 z-20 flex min-h-14 flex-none flex-wrap items-center gap-2 border-b px-3 pt-[calc(0.5rem+env(safe-area-inset-top))] pb-2 backdrop-blur sm:gap-3 sm:px-5"
725
730
  >
726
731
  {#if hasNav}
727
732
  <!-- `aria-controls` names the region this toggle opens. It is present
@@ -773,7 +778,7 @@
773
778
  class={cn(
774
779
  'flex min-w-0 items-center gap-2 sm:gap-3',
775
780
  controls.content
776
- ? 'order-last basis-full pb-2 xl:order-none xl:basis-auto xl:pb-0'
781
+ ? 'order-last basis-full xl:order-none xl:basis-auto'
777
782
  : 'hidden md:flex'
778
783
  )}
779
784
  data-testid="ds-shell-location"
@@ -16,5 +16,11 @@
16
16
  *
17
17
  * The bottom pad carries `env(safe-area-inset-bottom)` so the last row of a page
18
18
  * clears a home-indicator gesture bar.
19
+ *
20
+ * The trailing `::after` is `--ds-report-reserve`, the footprint the mounted
21
+ * report widget declares, so a page's last row can scroll clear of the button
22
+ * floating over its corner. An element rather than padding: a page taller than
23
+ * the content area overflows this box, which leaves its padding behind at the
24
+ * area's foot, while a last flex item follows the overflow to its end.
19
25
  */
20
- export declare const CONTENT_PADDING = "px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] sm:px-6 md:px-8 md:pt-7 md:pb-[calc(1.75rem+env(safe-area-inset-bottom))] 2xl:px-12";
26
+ export declare const CONTENT_PADDING = "px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] sm:px-6 md:px-8 md:pt-7 md:pb-[calc(1.75rem+env(safe-area-inset-bottom))] 2xl:px-12 after:block after:flex-none after:h-[var(--ds-report-reserve,0px)]";
@@ -16,5 +16,11 @@
16
16
  *
17
17
  * The bottom pad carries `env(safe-area-inset-bottom)` so the last row of a page
18
18
  * clears a home-indicator gesture bar.
19
+ *
20
+ * The trailing `::after` is `--ds-report-reserve`, the footprint the mounted
21
+ * report widget declares, so a page's last row can scroll clear of the button
22
+ * floating over its corner. An element rather than padding: a page taller than
23
+ * the content area overflows this box, which leaves its padding behind at the
24
+ * area's foot, while a last flex item follows the overflow to its end.
19
25
  */
20
- export const CONTENT_PADDING = 'px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] sm:px-6 md:px-8 md:pt-7 md:pb-[calc(1.75rem+env(safe-area-inset-bottom))] 2xl:px-12';
26
+ export const CONTENT_PADDING = 'px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] sm:px-6 md:px-8 md:pt-7 md:pb-[calc(1.75rem+env(safe-area-inset-bottom))] 2xl:px-12 after:block after:flex-none after:h-[var(--ds-report-reserve,0px)]';
@@ -220,9 +220,9 @@
220
220
  /** What a column sorts by; the running balance depends on the order, so never it. */
221
221
  const sorter = (c: { key: string; sort?: LedgerColumn<R>['sort']; text?: LedgerColumn<R>['text'] }) =>
222
222
  c.key === 'balance' ? undefined : (c.sort ?? c.text);
223
- /** Click a head to sort by it, largest or newest first; again to reverse. */
223
+ /** Click a head to sort by it, largest or newest first; again to reverse; a third time back to the default order. */
224
224
  function sortBy(key: string) {
225
- sort = sort?.key === key ? { key, dir: sort.dir === 1 ? -1 : 1 } : { key, dir: -1 };
225
+ sort = sort?.key !== key ? { key, dir: -1 } : sort.dir === -1 ? { key, dir: 1 } : null;
226
226
  }
227
227
  /** The sort in force: a column the viewer has since unticked no longer sorts. */
228
228
  const activeSort = $derived.by(() => {
@@ -231,8 +231,8 @@
231
231
  ? sort
232
232
  : null;
233
233
  });
234
- /** Until the viewer sorts, rows sit newest first in their period's groups. */
235
- const grouped = $derived(prefs.period !== 'none' && !activeSort);
234
+ /** A sort orders rows inside their period's groups; only `none` is the flat list. */
235
+ const grouped = $derived(prefs.period !== 'none');
236
236
  const BY_DAY = $derived(grouped && prefs.period === 'day');
237
237
 
238
238
  const shown = $derived(
@@ -262,8 +262,9 @@
262
262
  });
263
263
  const groups = $derived.by(() => {
264
264
  const by = activeSort && sorter(resolved.find((c) => c.key === activeSort.key)!);
265
- if (activeSort && by) return [{ key: '', rows: sortRows(rows, by, activeSort.dir) }];
266
- return groupRows(rows, prefs.period, fy);
265
+ const base = groupRows(rows, prefs.period, fy);
266
+ if (!activeSort || !by) return base;
267
+ return base.map((g) => ({ key: g.key, rows: sortRows(g.rows, by, activeSort.dir) }));
267
268
  });
268
269
  /** Closed groups, keyed with their period so a new period starts open. */
269
270
  const closed = new SvelteSet<string>();
@@ -923,7 +924,7 @@
923
924
  mine && 'text-foreground'
924
925
  )}
925
926
  aria-label={mine
926
- ? `Sorted by ${c.label}, ${mine.dir === -1 ? 'descending' : 'ascending'}. Click to reverse`
927
+ ? `Sorted by ${c.label}, ${mine.dir === -1 ? 'descending' : 'ascending'}. Click ${mine.dir === -1 ? 'to reverse' : 'for the default order'}`
927
928
  : `Sort by ${c.label}`}
928
929
  onclick={() => sortBy(c.key)}
929
930
  data-slot="ledger-sort"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poodle64/ui",
3
- "version": "2026.10.2",
3
+ "version": "2026.10.4",
4
4
  "description": "Household shared component layer: shadcn-svelte primitives (bits-ui) plus the composed page chrome every app builds its routes from, restyled by each app's @poodle64/design-tokens alias layer. One fix reaches every app.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "package": "@poodle64/ui",
4
- "version": "2026.10.2",
4
+ "version": "2026.10.4",
5
5
  "generatedBy": "scripts/generate-registry.mjs",
6
6
  "source": "scripts/situations.json + package source (DO NOT EDIT the outputs by hand)",
7
7
  "componentCount": 62,
@@ -1,7 +1,7 @@
1
1
  # @poodle64/ui — situation → component map
2
2
 
3
3
  <!-- GENERATED by scripts/generate-registry.mjs from scripts/situations.json + package source. DO NOT EDIT. -->
4
- Generated from `@poodle64/ui@2026.10.2`. 62 components, 18 situations.
4
+ Generated from `@poodle64/ui@2026.10.4`. 62 components, 18 situations.
5
5
 
6
6
  **Read this before writing a `<div>`.** Find the SITUATION you are in below, then compose the component named for it — do not hand-build it from raw `Card` or utility classes. Import is `import { Name } from '<import path>'`. Props marked `?` are optional. This map is the retrieval step the [`frontend-design` skill] makes mandatory; the [CHI 2026 study] measured composing-from-a-registry at 95% design-system compliance against 71% for writing the CSS from a prose style guide.
7
7