driggsby 0.3.1 → 0.3.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.
- package/dist/init/template-styles.js +10 -17
- package/dist/init/templates.js +102 -25
- package/package.json +3 -3
|
@@ -176,12 +176,13 @@ h2 {
|
|
|
176
176
|
font-variant-numeric: tabular-nums;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
/* The skeleton ships in the HTML so the first paint is the final layout
|
|
180
|
-
muted bars; the first render replaces it
|
|
181
|
-
bar's margins pad it to exactly
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
nothing shifts when data
|
|
179
|
+
/* The skeleton ships in the HTML so the first paint is the final layout
|
|
180
|
+
as muted bars; the first data render replaces it in a dissolve (see the
|
|
181
|
+
"Settling in" section of app.js). Each bar's margins pad it to exactly
|
|
182
|
+
the height of the text it stands in for, and the columns holding
|
|
183
|
+
stacked bars are flex so those margins never collapse — the skeleton
|
|
184
|
+
page and the data page measure identical, and nothing shifts when data
|
|
185
|
+
lands. */
|
|
185
186
|
.skeleton {
|
|
186
187
|
border-radius: var(--radius-bar);
|
|
187
188
|
background: var(--bg-mute);
|
|
@@ -230,18 +231,10 @@ h2 {
|
|
|
230
231
|
50% { opacity: 0.55; }
|
|
231
232
|
}
|
|
232
233
|
|
|
233
|
-
.
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
@keyframes fade-in {
|
|
238
|
-
from { opacity: 0; }
|
|
239
|
-
to { opacity: 1; }
|
|
240
|
-
}
|
|
241
|
-
|
|
234
|
+
/* The skeleton-to-data dissolve itself is app.js's "Settling in" View
|
|
235
|
+
Transition; it needs no CSS here and already sits out reduced motion. */
|
|
242
236
|
@media (prefers-reduced-motion: reduce) {
|
|
243
|
-
.skeleton
|
|
244
|
-
.fade-in {
|
|
237
|
+
.skeleton {
|
|
245
238
|
animation: none;
|
|
246
239
|
}
|
|
247
240
|
}
|
package/dist/init/templates.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// tool result shapes only in structure, so the scaffold's render functions
|
|
7
7
|
// work unchanged when live data replaces them. The HTML ships a skeleton
|
|
8
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
|
|
10
|
-
//
|
|
9
|
+
// script runs — is muted bars that the first render replaces at the exact
|
|
10
|
+
// same size, dissolving one into the other, so nothing shifts or pops.
|
|
11
11
|
export function indexHtml(slug) {
|
|
12
12
|
return `<!doctype html>
|
|
13
13
|
<html lang="en">
|
|
@@ -190,19 +190,6 @@ function element(tag, className, text) {
|
|
|
190
190
|
return node;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
// The page ships with a skeleton of this exact layout in its HTML — muted
|
|
194
|
-
// bars where the numbers will be — so the very first paint is already the
|
|
195
|
-
// final shape. The first render into a container replaces its skeleton and
|
|
196
|
-
// fades in, so data arriving reads as the page settling, never as content
|
|
197
|
-
// popping. classList.add is idempotent and re-adding a class never restarts
|
|
198
|
-
// its animation, so later live updates repaint in place without re-fading.
|
|
199
|
-
// aria-busy ships in the HTML so assistive tech hears "loading" until the
|
|
200
|
-
// first real content lands.
|
|
201
|
-
function revealOnce(container) {
|
|
202
|
-
container.classList.add("fade-in");
|
|
203
|
-
container.removeAttribute("aria-busy");
|
|
204
|
-
}
|
|
205
|
-
|
|
206
193
|
function renderOverview(result) {
|
|
207
194
|
const rollups = (result && result.summary_rollups) || {};
|
|
208
195
|
const container = document.getElementById("overview");
|
|
@@ -221,7 +208,9 @@ function renderOverview(result) {
|
|
|
221
208
|
);
|
|
222
209
|
container.append(stat);
|
|
223
210
|
}
|
|
224
|
-
|
|
211
|
+
// aria-busy ships in the HTML so assistive tech hears "loading" until
|
|
212
|
+
// the first content lands; every render clears it (harmless once gone).
|
|
213
|
+
container.removeAttribute("aria-busy");
|
|
225
214
|
}
|
|
226
215
|
|
|
227
216
|
function renderAccounts(result) {
|
|
@@ -231,7 +220,7 @@ function renderAccounts(result) {
|
|
|
231
220
|
container.replaceChildren();
|
|
232
221
|
if (accounts.length === 0) {
|
|
233
222
|
container.append(element("div", "empty-row", "No linked accounts yet."));
|
|
234
|
-
|
|
223
|
+
container.removeAttribute("aria-busy");
|
|
235
224
|
return;
|
|
236
225
|
}
|
|
237
226
|
for (const account of accounts) {
|
|
@@ -262,7 +251,99 @@ function renderAccounts(result) {
|
|
|
262
251
|
);
|
|
263
252
|
container.append(row);
|
|
264
253
|
}
|
|
265
|
-
|
|
254
|
+
container.removeAttribute("aria-busy");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Settling in. The first data a section shows replaces its skeleton in a
|
|
259
|
+
// dissolve: the browser snapshots the page, the paint lands, and old
|
|
260
|
+
// crossfades into new (a View Transition — pixels that did not change
|
|
261
|
+
// don't visibly change, so what reads as animating is the bars becoming
|
|
262
|
+
// numbers). The skeleton and the data measure identical (see styles.css),
|
|
263
|
+
// so the dissolve reads as the page coming into focus — never a blink, a
|
|
264
|
+
// shift, or a pop. Where the browser has no View Transitions, or the
|
|
265
|
+
// person prefers reduced motion, the swap is simply instant.
|
|
266
|
+
//
|
|
267
|
+
// The beat below covers sections whose data lands together (the common
|
|
268
|
+
// case). A section whose first data trails in later gets its own beat and
|
|
269
|
+
// its own dissolve; starting it skips whatever remained of the first one's
|
|
270
|
+
// animation — the paints always land, only the crossfade is cut short.
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
// How long a section's first paint may wait so sections whose data lands
|
|
274
|
+
// in the same beat dissolve in together instead of one by one.
|
|
275
|
+
const SETTLE_TOGETHER_MS = 50;
|
|
276
|
+
|
|
277
|
+
let pendingFirstPaints = [];
|
|
278
|
+
let settleFlushScheduled = false;
|
|
279
|
+
|
|
280
|
+
function settle(paint) {
|
|
281
|
+
const reducedMotion =
|
|
282
|
+
typeof window.matchMedia === "function" &&
|
|
283
|
+
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
284
|
+
if (typeof document.startViewTransition !== "function" || reducedMotion) {
|
|
285
|
+
paint();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
pendingFirstPaints.push(paint);
|
|
289
|
+
if (settleFlushScheduled) return; // this paint rides the beat already in flight
|
|
290
|
+
settleFlushScheduled = true;
|
|
291
|
+
setTimeout(() => {
|
|
292
|
+
// Collected inside the flush, which runs inside the transition
|
|
293
|
+
// callback, so a first result that lands between the beat ending and
|
|
294
|
+
// the snapshot being taken joins this dissolve instead of cutting it
|
|
295
|
+
// short with a second one.
|
|
296
|
+
const flush = () => {
|
|
297
|
+
const paints = pendingFirstPaints;
|
|
298
|
+
pendingFirstPaints = [];
|
|
299
|
+
settleFlushScheduled = false;
|
|
300
|
+
for (const apply of paints) {
|
|
301
|
+
// One section's render must never stop another's: the scaffold
|
|
302
|
+
// is meant to be edited, and a throw mid-edit stays that
|
|
303
|
+
// section's problem — it heals on its own next result.
|
|
304
|
+
try {
|
|
305
|
+
apply();
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error(error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
try {
|
|
312
|
+
const transition = document.startViewTransition(flush);
|
|
313
|
+
// A skipped transition (a hidden tab, a second dissolve starting)
|
|
314
|
+
// still runs flush; only the animation is dropped. Without this,
|
|
315
|
+
// the skip surfaces as an uncaught rejection in the console.
|
|
316
|
+
transition.ready.catch(() => {});
|
|
317
|
+
} catch (error) {
|
|
318
|
+
// If starting the transition itself threw, nothing will call
|
|
319
|
+
// flush; run it plain so the paints land and the beat resets.
|
|
320
|
+
console.error(error);
|
|
321
|
+
flush();
|
|
322
|
+
}
|
|
323
|
+
}, SETTLE_TOGETHER_MS);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Wraps a render function for its watch: the first result goes through
|
|
327
|
+
// settle() above, and only the latest result paints if more arrive while
|
|
328
|
+
// that dissolve is still pending. Every later result repaints in place
|
|
329
|
+
// with no animation at all.
|
|
330
|
+
function settledRenderer(render) {
|
|
331
|
+
let hasSettled = false;
|
|
332
|
+
let firstPaintQueued = false;
|
|
333
|
+
let latestResult;
|
|
334
|
+
return (result) => {
|
|
335
|
+
if (hasSettled) {
|
|
336
|
+
render(result);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
latestResult = result;
|
|
340
|
+
if (firstPaintQueued) return;
|
|
341
|
+
firstPaintQueued = true;
|
|
342
|
+
settle(() => {
|
|
343
|
+
hasSettled = true;
|
|
344
|
+
render(latestResult);
|
|
345
|
+
});
|
|
346
|
+
};
|
|
266
347
|
}
|
|
267
348
|
|
|
268
349
|
// ---------------------------------------------------------------------------
|
|
@@ -273,7 +354,7 @@ function renderAccounts(result) {
|
|
|
273
354
|
// shows its own notice explaining that the page runs with real data only
|
|
274
355
|
// inside Driggsby. Embedded, sample numbers never paint at all — the
|
|
275
356
|
// shipped skeleton holds the layout until the first real results replace
|
|
276
|
-
// it
|
|
357
|
+
// it in a dissolve (see "Settling in" above).
|
|
277
358
|
// ---------------------------------------------------------------------------
|
|
278
359
|
|
|
279
360
|
const standalone = window.parent === window;
|
|
@@ -284,12 +365,8 @@ if (standalone) {
|
|
|
284
365
|
}
|
|
285
366
|
|
|
286
367
|
if (window.driggsby) {
|
|
287
|
-
driggsby.watch("get_overview", {}, (
|
|
288
|
-
|
|
289
|
-
});
|
|
290
|
-
driggsby.watch("list_accounts", {}, (result) => {
|
|
291
|
-
renderAccounts(result);
|
|
292
|
-
});
|
|
368
|
+
driggsby.watch("get_overview", {}, settledRenderer(renderOverview));
|
|
369
|
+
driggsby.watch("list_accounts", {}, settledRenderer(renderAccounts));
|
|
293
370
|
}
|
|
294
371
|
`;
|
|
295
372
|
// "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.3.
|
|
3
|
+
"version": "0.3.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.3.
|
|
32
|
-
"@driggsby/sdk": "0.3.
|
|
31
|
+
"@driggsby/deploy": "0.3.2",
|
|
32
|
+
"@driggsby/sdk": "0.3.2"
|
|
33
33
|
}
|
|
34
34
|
}
|