driggsby 0.7.2 → 0.7.3

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.
@@ -11,9 +11,10 @@
11
11
  // (init-command.test.ts pins the two together): Driggsby paints that color
12
12
  // while the app loads.
13
13
  export const STYLES_CSS = `/* Driggsby's console palette, so this page reads as part of the app it
14
- opens inside. Change these together if you re-theme; keep --bg-app equal
15
- to the "background" in driggsby.json. The status tones are the
16
- console's own, for gains, losses, warnings, and a live dot. */
14
+ opens inside. Change these together if you re-theme (all but
15
+ --font-sans, which stays as it is); keep --bg-app equal to the
16
+ "background" in driggsby.json. The status tones are the console's own,
17
+ for gains, losses, warnings, and a live dot. */
17
18
  :root {
18
19
  color-scheme: dark;
19
20
  --bg-app: #000000;
@@ -36,13 +37,16 @@ export const STYLES_CSS = `/* Driggsby's console palette, so this page reads as
36
37
 
37
38
  /* Inter, which Driggsby serves at /-/inter.woff2 on this app's own origin
38
39
  (driggsby dev serves the same file there), so nothing loads from another
39
- site. Keep the page's text set in it: the Dashboards page shows a
40
- picture of this page, taken on a server with none of your fonts, and
41
- only a font the page loads itself looks the same there. Fonts from other
42
- sites are refused. To switch faces, put the font file in this folder,
43
- point an @font-face at it, name it first in --font-sans, and change the
44
- preload in index.html to match. Text waits for the font (font-display:
45
- block, preloaded) rather than flash another face first. */
40
+ site. Inter is the only font a dashboard uses: set all text with
41
+ var(--font-sans), keep that stack as it is (the faces after Inter only
42
+ stand in if the font is slow or fails to load, or lacks a character),
43
+ and name no other font anywhere else. A <canvas> or a chart library's
44
+ font setting can't read var(): name "Inter" there, followed only by
45
+ sans-serif, and draw a canvas after document.fonts.ready. The
46
+ Dashboards page shows a picture of this page, taken on a server with
47
+ none of your fonts, and any other face looks different there than on
48
+ the live page. Text waits for the font (font-display: block,
49
+ preloaded) rather than flash another face first. */
46
50
  @font-face {
47
51
  font-family: "Inter";
48
52
  src: url("/-/inter.woff2") format("woff2");
@@ -65,6 +69,15 @@ body {
65
69
  -webkit-font-smoothing: antialiased;
66
70
  }
67
71
 
72
+ /* Browsers set form controls in the system font on their own, whatever
73
+ the body's; these take the page's Inter instead. */
74
+ button,
75
+ input,
76
+ select,
77
+ textarea {
78
+ font: inherit;
79
+ }
80
+
68
81
  /* A compact column: a few figures and a short list read best close
69
82
  together. The console's own pages run wider (75rem); widen this as the
70
83
  page grows. */
@@ -114,6 +127,10 @@ h2 {
114
127
  }
115
128
 
116
129
  .stat {
130
+ /* The figure inside sizes itself to this card (see .stat-value), so
131
+ the card takes its width from the grid, never from its content: in a
132
+ content-sized track it would collapse. */
133
+ container-type: inline-size;
117
134
  display: flex;
118
135
  flex-direction: column;
119
136
  padding: 14px 16px;
@@ -129,15 +146,34 @@ h2 {
129
146
 
130
147
  .stat-value {
131
148
  margin-top: 2px;
149
+ /* 20px, and 20px wherever container units aren't known; the @supports
150
+ rule below shrinks a figure too long for its card. The line stays
151
+ 30px whatever size the figure draws at, so it measures the same as
152
+ the skeleton bar it replaces. */
132
153
  font-size: 20px;
154
+ line-height: 30px;
133
155
  font-weight: 600;
134
156
  letter-spacing: -0.01em;
135
157
  font-variant-numeric: tabular-nums;
136
- /* A currency string has no natural break, and a clipped balance is a
137
- wrong number. If a value ever outgrows its cell, it wraps. */
158
+ /* Past the 12px floor a figure wraps rather than spill out of its
159
+ card: a clipped balance is a wrong number. */
138
160
  overflow-wrap: anywhere;
139
161
  }
140
162
 
163
+ /* A figure keeps 20px unless it is too long for its card, and then
164
+ shrinks just enough to stay one number: its length (--chars, which
165
+ renderOverview sets) at 0.6em a character, the widest these figures
166
+ run with a little room, fills at most the card (100cqi). A short
167
+ balance never shrinks; $99,999,999.99 in a desktop card draws near
168
+ 17px. Inside @supports, so a browser without container units keeps
169
+ 20px: outside it, the var() would let the line override 20px and
170
+ then fail. */
171
+ @supports (width: 1cqi) {
172
+ .stat-value {
173
+ font-size: clamp(12px, calc(100cqi / (var(--chars, 1) * 0.6)), 20px);
174
+ }
175
+ }
176
+
141
177
  #accounts {
142
178
  border: 1px solid var(--hairline);
143
179
  border-radius: var(--radius-card);
@@ -162,6 +198,28 @@ h2 {
162
198
  color: var(--text-secondary);
163
199
  }
164
200
 
201
+ /* A section whose call failed says why in its own place (see "When a call
202
+ fails" in app.js): in the list, in a row's inset under any rows it
203
+ keeps; across the stat row, as a card of its own. A long reason (one
204
+ can quote your SQL) wraps inside it. */
205
+ .problem {
206
+ padding: 14px 16px;
207
+ color: var(--text-warning);
208
+ overflow-wrap: anywhere;
209
+ }
210
+
211
+ .stat-grid > .problem {
212
+ grid-column: 1 / -1;
213
+ border: 1px solid var(--hairline);
214
+ border-radius: var(--radius-card);
215
+ background: var(--bg-card);
216
+ }
217
+
218
+ .account-row + .problem,
219
+ .empty-row + .problem {
220
+ border-top: 1px solid var(--hairline);
221
+ }
222
+
165
223
  /* The institution's initial on a round pill, as the console draws it. */
166
224
  .account-glyph {
167
225
  flex: none;
@@ -88,11 +88,13 @@ export const APP_JS = `// Your app's code. Edit anything — driggsby dev reload
88
88
  //
89
89
  // Reading data is one call:
90
90
  //
91
- // driggsby.watch(tool, params, callback)
91
+ // driggsby.watch(tool, params, callback, { onError })
92
92
  //
93
93
  // Each watch is a live subscription: the callback runs with a fresh result
94
- // whenever your Driggsby data changes. The sample data below matches the
95
- // real result shapes, so the render functions work unchanged either way.
94
+ // whenever your Driggsby data changes, and onError runs instead when a call
95
+ // fails (see "When a call fails" below; watchSection wires one section
96
+ // that way, so add yours with it). The sample data below matches the real
97
+ // result shapes, so the render functions work unchanged either way.
96
98
  //
97
99
  // These are the tools an app can watch — read-only, nothing else responds:
98
100
  //
@@ -204,10 +206,11 @@ function renderOverview(result) {
204
206
  ];
205
207
  for (const [label, value] of stats) {
206
208
  const stat = element("div", "stat");
207
- stat.append(
208
- element("div", "stat-label", label),
209
- element("div", "stat-value", formatMoney(value))
210
- );
209
+ const figure = element("div", "stat-value", formatMoney(value));
210
+ // Its length, so a long figure can shrink to fit its card while a
211
+ // short one keeps its full size (see .stat-value in styles.css).
212
+ figure.style.setProperty("--chars", String(figure.textContent.length));
213
+ stat.append(element("div", "stat-label", label), figure);
211
214
  container.append(stat);
212
215
  }
213
216
  // aria-busy ships in the HTML so assistive tech hears "loading" until
@@ -348,6 +351,60 @@ function settledRenderer(render) {
348
351
  };
349
352
  }
350
353
 
354
+ // ---------------------------------------------------------------------------
355
+ // When a call fails. A watch whose call fails never runs its callback; it
356
+ // runs onError instead, with { message, kind }, once for each new failure —
357
+ // after Driggsby has already retried a call that failed because it was busy
358
+ // or slow. The section says why where its data would be: in place of its
359
+ // skeleton, so a failure never looks like a page still loading, or under
360
+ // the data it already shows, which stays. The watch asks again on its own
361
+ // the next time your data changes, and the next good result repaints the
362
+ // section, reason and all. A section counts as still loading while its
363
+ // container is aria-busy="true": the HTML ships every container that way,
364
+ // and every render clears it and replaces everything in its container,
365
+ // so keep all three true for a section you add.
366
+ // ---------------------------------------------------------------------------
367
+
368
+ function showProblem(containerId, error) {
369
+ const container = document.getElementById(containerId);
370
+ const message =
371
+ error && typeof error.message === "string" && error.message
372
+ ? error.message
373
+ : "This didn't load. It tries again when your data next changes.";
374
+ const note = element("div", "problem", message);
375
+ note.setAttribute("role", "status");
376
+ if (container.getAttribute("aria-busy") === "true") {
377
+ container.replaceChildren(note);
378
+ container.removeAttribute("aria-busy");
379
+ return;
380
+ }
381
+ const earlier = container.querySelector(".problem");
382
+ if (earlier) earlier.remove();
383
+ container.append(note);
384
+ }
385
+
386
+ // One section's live data: its watch, its render, and its failures. A
387
+ // failure that lands while the section's first result waits for its
388
+ // dissolve still shows once that result paints, under it. It returns the
389
+ // watch's stop function: call it before watching the same section again
390
+ // with new params.
391
+ function watchSection(tool, params, containerId, render) {
392
+ let problem = null;
393
+ const paint = settledRenderer((result) => {
394
+ render(result);
395
+ if (problem) showProblem(containerId, problem);
396
+ });
397
+ return driggsby.watch(tool, params, (result) => {
398
+ problem = null;
399
+ paint(result);
400
+ }, {
401
+ onError: (error) => {
402
+ problem = error;
403
+ showProblem(containerId, error);
404
+ }
405
+ });
406
+ }
407
+
351
408
  // ---------------------------------------------------------------------------
352
409
  // Live data. window.driggsby exists when the Driggsby SDK loaded — inside
353
410
  // Driggsby, or under driggsby dev. Whether anything embeds this page is
@@ -367,8 +424,8 @@ if (standalone) {
367
424
  }
368
425
 
369
426
  if (window.driggsby) {
370
- driggsby.watch("get_overview", {}, settledRenderer(renderOverview));
371
- driggsby.watch("list_accounts", {}, settledRenderer(renderAccounts));
427
+ watchSection("get_overview", {}, "overview", renderOverview);
428
+ watchSection("list_accounts", {}, "accounts", renderAccounts);
372
429
  }
373
430
  `;
374
431
  // "background" is the template's own page background (styles.css
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "driggsby",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "Set up and build on Driggsby, the MCP server for personal finance.",
5
5
  "license": "Apache-2.0 AND OFL-1.1",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "node": ">=18"
31
31
  },
32
32
  "dependencies": {
33
- "@driggsby/deploy": "0.7.2",
34
- "@driggsby/sdk": "0.7.2"
33
+ "@driggsby/deploy": "0.7.3",
34
+ "@driggsby/sdk": "0.7.3"
35
35
  }
36
36
  }