driggsby 0.2.1 → 0.2.2

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.
@@ -4,7 +4,10 @@
4
4
  // (`driggsby dev` locally, or the Driggsby console after a deploy). The
5
5
  // sample values below are invented for the template and match the real
6
6
  // tool result shapes only in structure, so the scaffold's render functions
7
- // work unchanged when live data replaces them.
7
+ // work unchanged when live data replaces them. The HTML ships a skeleton
8
+ // of the final layout so the first paint — in every context, before any
9
+ // script runs — is muted bars that the first render replaces, fading in
10
+ // at the exact same size so nothing shifts.
8
11
  export function indexHtml(slug) {
9
12
  return `<!doctype html>
10
13
  <html lang="en">
@@ -21,12 +24,56 @@ export function indexHtml(slug) {
21
24
  <p class="sample-note" id="sample-note" hidden>Sample data — your real
22
25
  accounts appear when this app runs inside Driggsby.</p>
23
26
  </header>
27
+ <!-- The bars below are skeleton placeholders. app.js replaces them
28
+ with data on its first render; edit the render functions there,
29
+ and keep the four stat bars in step with what renderOverview
30
+ draws. (The account rows are just a plausible list length —
31
+ the real count comes from the data.) -->
24
32
  <section aria-label="Overview">
25
- <div class="stat-grid" id="overview"></div>
33
+ <div class="stat-grid" id="overview" aria-busy="true">
34
+ <div class="stat">
35
+ <div class="skeleton skeleton-label"></div>
36
+ <div class="skeleton skeleton-value"></div>
37
+ </div>
38
+ <div class="stat">
39
+ <div class="skeleton skeleton-label"></div>
40
+ <div class="skeleton skeleton-value"></div>
41
+ </div>
42
+ <div class="stat">
43
+ <div class="skeleton skeleton-label"></div>
44
+ <div class="skeleton skeleton-value"></div>
45
+ </div>
46
+ <div class="stat">
47
+ <div class="skeleton skeleton-label"></div>
48
+ <div class="skeleton skeleton-value"></div>
49
+ </div>
50
+ </div>
26
51
  </section>
27
52
  <section aria-label="Accounts">
28
53
  <h2>Accounts</h2>
29
- <div id="accounts"></div>
54
+ <div id="accounts" aria-busy="true">
55
+ <div class="account-row">
56
+ <div class="account-names">
57
+ <div class="skeleton skeleton-name"></div>
58
+ <div class="skeleton skeleton-institution"></div>
59
+ </div>
60
+ <div class="skeleton skeleton-balance"></div>
61
+ </div>
62
+ <div class="account-row">
63
+ <div class="account-names">
64
+ <div class="skeleton skeleton-name"></div>
65
+ <div class="skeleton skeleton-institution"></div>
66
+ </div>
67
+ <div class="skeleton skeleton-balance"></div>
68
+ </div>
69
+ <div class="account-row">
70
+ <div class="account-names">
71
+ <div class="skeleton skeleton-name"></div>
72
+ <div class="skeleton skeleton-institution"></div>
73
+ </div>
74
+ <div class="skeleton skeleton-balance"></div>
75
+ </div>
76
+ </div>
30
77
  </section>
31
78
  </main>
32
79
  <script type="module" src="/-/driggsby-sdk.js"></script>
@@ -104,6 +151,19 @@ function element(tag, className, text) {
104
151
  return node;
105
152
  }
106
153
 
154
+ // The page ships with a skeleton of this exact layout in its HTML — muted
155
+ // bars where the numbers will be — so the very first paint is already the
156
+ // final shape. The first render into a container replaces its skeleton and
157
+ // fades in, so data arriving reads as the page settling, never as content
158
+ // popping. classList.add is idempotent and re-adding a class never restarts
159
+ // its animation, so later live updates repaint in place without re-fading.
160
+ // aria-busy ships in the HTML so assistive tech hears "loading" until the
161
+ // first real content lands.
162
+ function revealOnce(container) {
163
+ container.classList.add("fade-in");
164
+ container.removeAttribute("aria-busy");
165
+ }
166
+
107
167
  function renderOverview(result) {
108
168
  const rollups = (result && result.summary_rollups) || {};
109
169
  const container = document.getElementById("overview");
@@ -122,6 +182,7 @@ function renderOverview(result) {
122
182
  );
123
183
  container.append(stat);
124
184
  }
185
+ revealOnce(container);
125
186
  }
126
187
 
127
188
  function renderAccounts(result) {
@@ -143,32 +204,32 @@ function renderAccounts(result) {
143
204
  row.append(names, element("div", "account-balance", formatMoney(account.current_balance)));
144
205
  container.append(row);
145
206
  }
207
+ revealOnce(container);
146
208
  }
147
209
 
148
210
  // ---------------------------------------------------------------------------
149
211
  // Live data. window.driggsby exists when the Driggsby SDK loaded — inside
150
212
  // Driggsby, or under driggsby dev. Whether anything embeds this page is
151
213
  // knowable synchronously: opened directly in a tab, no host will ever
152
- // answer, so the sample data paints right away and the note explaining it
153
- // is revealed (it ships hidden in the HTML, so an embedded page never
154
- // shows it, not even for the moment before this script runs). Embedded,
155
- // sample numbers never paint at all — a muted loading line holds the
156
- // accounts area until real data arrives.
214
+ // answer, so the sample data replaces the skeleton right away and the note
215
+ // explaining it is revealed (it ships hidden in the HTML, so an embedded
216
+ // page never shows it, not even for the moment before this script runs).
217
+ // Embedded, sample numbers never paint at all — the shipped skeleton holds
218
+ // the layout until the first real results replace it and fade in.
157
219
  // ---------------------------------------------------------------------------
158
220
 
159
221
  const standalone = window.parent === window;
160
222
  const note = document.getElementById("sample-note");
161
223
 
162
224
  if (standalone) {
225
+ // The note and the sample values paint together (this whole branch is
226
+ // one synchronous task). The reveal is kept first so an edit that ever
227
+ // makes rendering async can't paint values before their label.
228
+ if (note) note.hidden = false;
163
229
  renderOverview(SAMPLE_OVERVIEW);
164
230
  renderAccounts(SAMPLE_ACCOUNTS);
165
- if (note) note.hidden = false;
166
- } else {
167
- if (note) note.remove();
168
- const accounts = document.getElementById("accounts");
169
- if (accounts) {
170
- accounts.replaceChildren(element("div", "loading-note", "Loading your accounts…"));
171
- }
231
+ } else if (note) {
232
+ note.remove();
172
233
  }
173
234
 
174
235
  if (window.driggsby) {
@@ -235,10 +296,64 @@ h2 {
235
296
  display: none;
236
297
  }
237
298
 
238
- .loading-note {
239
- margin-top: 8px;
240
- color: var(--text-muted);
241
- font-size: 13px;
299
+ /* The skeleton ships in the HTML so the first paint is the final layout as
300
+ muted bars; the first render replaces it and fades in (see app.js). The
301
+ bar margins make each skeleton block exactly the height of the text it
302
+ stands in for, so nothing shifts when data lands. */
303
+ .skeleton {
304
+ border-radius: 4px;
305
+ background: var(--hairline);
306
+ animation: skeleton-pulse 1.6s ease-in-out infinite;
307
+ }
308
+
309
+ .skeleton-label {
310
+ width: 64px;
311
+ height: 12px;
312
+ margin: 3.75px 0 0;
313
+ }
314
+
315
+ .skeleton-value {
316
+ width: 96px;
317
+ height: 20px;
318
+ margin: 8.75px 0 5px;
319
+ }
320
+
321
+ .skeleton-name {
322
+ width: 150px;
323
+ height: 14px;
324
+ margin: 4px 0 8px;
325
+ }
326
+
327
+ .skeleton-institution {
328
+ width: 110px;
329
+ height: 12px;
330
+ margin: 4px 0;
331
+ }
332
+
333
+ .skeleton-balance {
334
+ width: 72px;
335
+ height: 14px;
336
+ }
337
+
338
+ @keyframes skeleton-pulse {
339
+ 0%, 100% { opacity: 1; }
340
+ 50% { opacity: 0.55; }
341
+ }
342
+
343
+ .fade-in {
344
+ animation: fade-in 200ms ease-out;
345
+ }
346
+
347
+ @keyframes fade-in {
348
+ from { opacity: 0; }
349
+ to { opacity: 1; }
350
+ }
351
+
352
+ @media (prefers-reduced-motion: reduce) {
353
+ .skeleton,
354
+ .fade-in {
355
+ animation: none;
356
+ }
242
357
  }
243
358
 
244
359
  .stat-grid {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "driggsby",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Set up and build on Driggsby, the MCP server for personal finance.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  "node": ">=18"
29
29
  },
30
30
  "dependencies": {
31
- "@driggsby/deploy": "0.2.1",
32
- "@driggsby/sdk": "0.2.1"
31
+ "@driggsby/deploy": "0.2.2",
32
+ "@driggsby/sdk": "0.2.2"
33
33
  }
34
34
  }