hearald 0.1.2 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.3] - 2026-11-08
11
+
12
+ ### Fixed
13
+
14
+ - Fix the dashboard chart's initial loading state when the app does not have a `users` field.
15
+ - Fix the dashboard's timezone to match the server's daily bucketing (Pacific time).
16
+
10
17
  ## [0.1.2] - 2026-10-26
11
18
 
12
19
  ### Fixed
package/README.md CHANGED
@@ -117,6 +117,8 @@ const statDefinitions = {
117
117
  list of tiles in `fields` (or `stats`); a tile is `{ key, label, type }` with `type` one of `number`,
118
118
  `percent` or `histogram`, and an optional `span`. `chartContexts` (`[{ id, menuName }]`) fills the chart's
119
119
  "Context" menu. Both are optional; a dashboard with no `ui` shows only the chart.
120
+ The chart starts on the `users` field and the `all` context when the app has them, otherwise on the
121
+ first field and the first `chartContexts` entry. The page's date defaults to today in Pacific time.
120
122
 
121
123
  Names with dots become nested objects in the result. `hidden: true` leaves a metric out of the output
122
124
  (useful for sets that only feed a computed metric). A computed metric that names a formula that does
@@ -405,6 +405,36 @@ th {
405
405
 
406
406
  var m=e.bind(_);
407
407
 
408
+ // A Date whose UTC fields hold the current Pacific wall-clock time, so `iso()` (which reads UTC
409
+ // fields) names the Pacific day. The instant it represents is not "now"; it is only for day buckets.
410
+ const nowAsPstDate = (now = new Date()) => {
411
+ const parts = Object.fromEntries(
412
+ new Intl.DateTimeFormat("en-US", {
413
+ timeZone: "America/Los_Angeles",
414
+ hourCycle: "h23",
415
+ year: "numeric",
416
+ month: "numeric",
417
+ day: "numeric",
418
+ hour: "numeric",
419
+ minute: "numeric",
420
+ second: "numeric",
421
+ })
422
+ .formatToParts(now)
423
+ .map((p) => [p.type, p.value])
424
+ );
425
+ return new Date(
426
+ Date.UTC(
427
+ Number(parts.year),
428
+ Number(parts.month) - 1,
429
+ Number(parts.day),
430
+ Number(parts.hour),
431
+ Number(parts.minute),
432
+ Number(parts.second),
433
+ now.getMilliseconds()
434
+ )
435
+ );
436
+ };
437
+
408
438
  function iso(time) {
409
439
  return [
410
440
  time.getUTCFullYear(),
@@ -413,6 +443,10 @@ th {
413
443
  ].join("-");
414
444
  }
415
445
 
446
+ // Today's calendar date in Pacific time as YYYY-MM-DD (daylight saving included), the same day
447
+ // buckets the server uses. The dashboard page defaults its date to this, not the browser's UTC date.
448
+ const pacificToday = (now = new Date()) => iso(nowAsPstDate(now));
449
+
416
450
  /* START methods for fetching data from the server */
417
451
 
418
452
  const dataCache = {};
@@ -485,6 +519,18 @@ th {
485
519
 
486
520
  /* END methods for fetching data from the server */
487
521
 
522
+ // Keeps the preferred chart field/context when they exist, otherwise falls back to the first
523
+ // available one, so the menus (which show the first option when nothing matches) and the chart agree.
524
+ function resolveChartDefaults({ chartField, chartContext, stats, contexts }) {
525
+ const fields = Object.keys(stats?.metrics?.all || {});
526
+ const contextIds = (contexts || []).map((c) => c.id);
527
+ return {
528
+ chartField: fields.length && !fields.includes(chartField) ? fields[0] : chartField,
529
+ chartContext:
530
+ contextIds.length && !contextIds.includes(chartContext) ? contextIds[0] : chartContext,
531
+ };
532
+ }
533
+
488
534
  /* START helper methods */
489
535
 
490
536
  const getJson = async (url) => {
@@ -622,8 +668,6 @@ th {
622
668
  }
623
669
  }
624
670
 
625
- const DATE = new Date().toISOString().split("T")[0];
626
-
627
671
  const Logo = () => m`
628
672
  <svg
629
673
  width="256"
@@ -703,7 +747,7 @@ th {
703
747
  super(props);
704
748
  this.state = {
705
749
  numDays: 30,
706
- date: DATE,
750
+ date: pacificToday(),
707
751
  chartField: "users",
708
752
  chartContext: "all",
709
753
  uiSectionsLoaded: false,
@@ -713,25 +757,22 @@ th {
713
757
  async componentDidMount() {
714
758
  await fetchUI();
715
759
  this.setState({ uiSectionsLoaded: true });
716
- fetchData({
760
+ // The chart waits for the stats so its field and context can be checked against them.
761
+ let stats = null;
762
+ await fetchData({
717
763
  numDays: this.state.numDays,
718
764
  date: this.state.date,
719
765
  getDataFn,
720
- onLoadComplete: (stats) => this.setState({ stats }),
766
+ onLoadComplete: (s) => (stats = s),
721
767
  });
722
- fetchChartData({
723
- numDays: this.state.numDays,
724
- date: this.state.date,
725
- chartContext: this.state.chartContext,
768
+ const { chartField, chartContext } = resolveChartDefaults({
726
769
  chartField: this.state.chartField,
727
- getDataFn,
728
- onLoadProgress: (i, n) => {
729
- this.setState({ chartData: { loading: { max: n, value: i } } });
730
- },
731
- onLoadComplete: (data, max) => {
732
- this.setState({ chartData: { loading: false, data, max } });
733
- },
770
+ chartContext: this.state.chartContext,
771
+ stats,
772
+ contexts: UI.chartContexts,
734
773
  });
774
+ this.setState({ stats, chartField, chartContext });
775
+ this.fetchChartData({ chartField, chartContext });
735
776
  }
736
777
 
737
778
  async fetchChartData({ numDays, date, chartContext, chartField }) {
package/dist/index.js CHANGED
@@ -7,6 +7,36 @@
7
7
 
8
8
  var m=e.bind(_);
9
9
 
10
+ // A Date whose UTC fields hold the current Pacific wall-clock time, so `iso()` (which reads UTC
11
+ // fields) names the Pacific day. The instant it represents is not "now"; it is only for day buckets.
12
+ const nowAsPstDate = (now = new Date()) => {
13
+ const parts = Object.fromEntries(
14
+ new Intl.DateTimeFormat("en-US", {
15
+ timeZone: "America/Los_Angeles",
16
+ hourCycle: "h23",
17
+ year: "numeric",
18
+ month: "numeric",
19
+ day: "numeric",
20
+ hour: "numeric",
21
+ minute: "numeric",
22
+ second: "numeric",
23
+ })
24
+ .formatToParts(now)
25
+ .map((p) => [p.type, p.value])
26
+ );
27
+ return new Date(
28
+ Date.UTC(
29
+ Number(parts.year),
30
+ Number(parts.month) - 1,
31
+ Number(parts.day),
32
+ Number(parts.hour),
33
+ Number(parts.minute),
34
+ Number(parts.second),
35
+ now.getMilliseconds()
36
+ )
37
+ );
38
+ };
39
+
10
40
  function iso(time) {
11
41
  return [
12
42
  time.getUTCFullYear(),
@@ -15,6 +45,10 @@
15
45
  ].join("-");
16
46
  }
17
47
 
48
+ // Today's calendar date in Pacific time as YYYY-MM-DD (daylight saving included), the same day
49
+ // buckets the server uses. The dashboard page defaults its date to this, not the browser's UTC date.
50
+ const pacificToday = (now = new Date()) => iso(nowAsPstDate(now));
51
+
18
52
  /* START methods for fetching data from the server */
19
53
 
20
54
  const dataCache = {};
@@ -87,6 +121,18 @@
87
121
 
88
122
  /* END methods for fetching data from the server */
89
123
 
124
+ // Keeps the preferred chart field/context when they exist, otherwise falls back to the first
125
+ // available one, so the menus (which show the first option when nothing matches) and the chart agree.
126
+ function resolveChartDefaults({ chartField, chartContext, stats, contexts }) {
127
+ const fields = Object.keys(stats?.metrics?.all || {});
128
+ const contextIds = (contexts || []).map((c) => c.id);
129
+ return {
130
+ chartField: fields.length && !fields.includes(chartField) ? fields[0] : chartField,
131
+ chartContext:
132
+ contextIds.length && !contextIds.includes(chartContext) ? contextIds[0] : chartContext,
133
+ };
134
+ }
135
+
90
136
  /* START helper methods */
91
137
 
92
138
  const getJson = async (url) => {
@@ -224,8 +270,6 @@
224
270
  }
225
271
  }
226
272
 
227
- const DATE = new Date().toISOString().split("T")[0];
228
-
229
273
  const Logo = () => m`
230
274
  <svg
231
275
  width="256"
@@ -305,7 +349,7 @@
305
349
  super(props);
306
350
  this.state = {
307
351
  numDays: 30,
308
- date: DATE,
352
+ date: pacificToday(),
309
353
  chartField: "users",
310
354
  chartContext: "all",
311
355
  uiSectionsLoaded: false,
@@ -315,25 +359,22 @@
315
359
  async componentDidMount() {
316
360
  await fetchUI();
317
361
  this.setState({ uiSectionsLoaded: true });
318
- fetchData({
362
+ // The chart waits for the stats so its field and context can be checked against them.
363
+ let stats = null;
364
+ await fetchData({
319
365
  numDays: this.state.numDays,
320
366
  date: this.state.date,
321
367
  getDataFn,
322
- onLoadComplete: (stats) => this.setState({ stats }),
368
+ onLoadComplete: (s) => (stats = s),
323
369
  });
324
- fetchChartData({
325
- numDays: this.state.numDays,
326
- date: this.state.date,
327
- chartContext: this.state.chartContext,
370
+ const { chartField, chartContext } = resolveChartDefaults({
328
371
  chartField: this.state.chartField,
329
- getDataFn,
330
- onLoadProgress: (i, n) => {
331
- this.setState({ chartData: { loading: { max: n, value: i } } });
332
- },
333
- onLoadComplete: (data, max) => {
334
- this.setState({ chartData: { loading: false, data, max } });
335
- },
372
+ chartContext: this.state.chartContext,
373
+ stats,
374
+ contexts: UI.chartContexts,
336
375
  });
376
+ this.setState({ stats, chartField, chartContext });
377
+ this.fetchChartData({ chartField, chartContext });
337
378
  }
338
379
 
339
380
  async fetchChartData({ numDays, date, chartContext, chartField }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hearald",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "A single server drop-in file system based metrics and analytics library.",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -7,10 +7,10 @@ import {
7
7
  getData,
8
8
  fetchChartData,
9
9
  fetchData,
10
+ resolveChartDefaults,
10
11
  } from "./util.js";
11
12
  import Tile from "./Tile.js";
12
-
13
- const DATE = new Date().toISOString().split("T")[0];
13
+ import { pacificToday } from "../../shared/utils.js";
14
14
 
15
15
  const Logo = () => html`
16
16
  <svg
@@ -91,7 +91,7 @@ class App extends Component {
91
91
  super(props);
92
92
  this.state = {
93
93
  numDays: 30,
94
- date: DATE,
94
+ date: pacificToday(),
95
95
  chartField: "users",
96
96
  chartContext: "all",
97
97
  uiSectionsLoaded: false,
@@ -101,25 +101,22 @@ class App extends Component {
101
101
  async componentDidMount() {
102
102
  await fetchUI();
103
103
  this.setState({ uiSectionsLoaded: true });
104
- fetchData({
104
+ // The chart waits for the stats so its field and context can be checked against them.
105
+ let stats = null;
106
+ await fetchData({
105
107
  numDays: this.state.numDays,
106
108
  date: this.state.date,
107
109
  getDataFn,
108
- onLoadComplete: (stats) => this.setState({ stats }),
110
+ onLoadComplete: (s) => (stats = s),
109
111
  });
110
- fetchChartData({
111
- numDays: this.state.numDays,
112
- date: this.state.date,
113
- chartContext: this.state.chartContext,
112
+ const { chartField, chartContext } = resolveChartDefaults({
114
113
  chartField: this.state.chartField,
115
- getDataFn,
116
- onLoadProgress: (i, n) => {
117
- this.setState({ chartData: { loading: { max: n, value: i } } });
118
- },
119
- onLoadComplete: (data, max) => {
120
- this.setState({ chartData: { loading: false, data, max } });
121
- },
114
+ chartContext: this.state.chartContext,
115
+ stats,
116
+ contexts: UI.chartContexts,
122
117
  });
118
+ this.setState({ stats, chartField, chartContext });
119
+ this.fetchChartData({ chartField, chartContext });
123
120
  }
124
121
 
125
122
  async fetchChartData({ numDays, date, chartContext, chartField }) {
@@ -72,6 +72,18 @@ export async function fetchData({
72
72
 
73
73
  /* END methods for fetching data from the server */
74
74
 
75
+ // Keeps the preferred chart field/context when they exist, otherwise falls back to the first
76
+ // available one, so the menus (which show the first option when nothing matches) and the chart agree.
77
+ export function resolveChartDefaults({ chartField, chartContext, stats, contexts }) {
78
+ const fields = Object.keys(stats?.metrics?.all || {});
79
+ const contextIds = (contexts || []).map((c) => c.id);
80
+ return {
81
+ chartField: fields.length && !fields.includes(chartField) ? fields[0] : chartField,
82
+ chartContext:
83
+ contextIds.length && !contextIds.includes(chartContext) ? contextIds[0] : chartContext,
84
+ };
85
+ }
86
+
75
87
  /* START helper methods */
76
88
 
77
89
  export const getJson = async (url) => {
@@ -65,3 +65,7 @@ export function defaultEndDate(now = nowAsPstDate()) {
65
65
  yesterday.setUTCHours(0, 0, 0, 0);
66
66
  return yesterday;
67
67
  }
68
+
69
+ // Today's calendar date in Pacific time as YYYY-MM-DD (daylight saving included), the same day
70
+ // buckets the server uses. The dashboard page defaults its date to this, not the browser's UTC date.
71
+ export const pacificToday = (now = new Date()) => iso(nowAsPstDate(now));