@validation-os/dashboard 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Benji Fisher
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,37 @@
1
+ import * as react from 'react';
2
+ import { Collection } from '@validation-os/core';
3
+
4
+ type Counts = Partial<Record<Collection, number>>;
5
+ interface UseCountsResult {
6
+ counts: Counts | null;
7
+ loading: boolean;
8
+ error: string | null;
9
+ refresh: () => void;
10
+ }
11
+ /**
12
+ * Client hook: fetch per-register counts from the API (`GET {basePath}/counts`).
13
+ * The API reads Firestore server-side through the adapter; this only speaks
14
+ * HTTP, so no backend credentials ever reach the browser.
15
+ */
16
+ declare function useCounts(basePath?: string): UseCountsResult;
17
+
18
+ interface RegisterCountsProps {
19
+ counts: Counts;
20
+ /** Optional caption under the tiles (e.g. the backend the numbers came from). */
21
+ caption?: string;
22
+ }
23
+ /**
24
+ * A glanceable grid of stat tiles — one per register, the register's row count
25
+ * as the hero number. Presentational: the caller reads the counts (server-side,
26
+ * through the adapter) and hands them in. Styled with Tailwind utility classes;
27
+ * the host app provides Tailwind.
28
+ */
29
+ declare function RegisterCounts({ counts, caption }: RegisterCountsProps): react.JSX.Element;
30
+
31
+ /** Plain-language labels — the register is a surface a non-technical
32
+ * teammate meets, so no jargon and no code-y plurals. */
33
+ declare const REGISTER_LABEL: Record<Collection, string>;
34
+ /** The order tiles read left-to-right, top-to-bottom. */
35
+ declare const REGISTER_ORDER: Collection[];
36
+
37
+ export { type Counts, REGISTER_LABEL, REGISTER_ORDER, RegisterCounts, type RegisterCountsProps, type UseCountsResult, useCounts };
package/dist/index.js ADDED
@@ -0,0 +1,84 @@
1
+ "use client";
2
+
3
+ // src/labels.ts
4
+ var REGISTER_LABEL = {
5
+ assumptions: "Assumptions",
6
+ experiments: "Experiments",
7
+ readings: "Readings",
8
+ goals: "Goals",
9
+ decisions: "Decisions",
10
+ glossary: "Glossary",
11
+ people: "People"
12
+ };
13
+ var REGISTER_ORDER = [
14
+ "assumptions",
15
+ "experiments",
16
+ "readings",
17
+ "goals",
18
+ "decisions",
19
+ "glossary",
20
+ "people"
21
+ ];
22
+
23
+ // src/register-counts.tsx
24
+ import { jsx, jsxs } from "react/jsx-runtime";
25
+ function RegisterCounts({ counts, caption }) {
26
+ const registers = REGISTER_ORDER.filter(
27
+ (r) => counts[r] !== void 0
28
+ );
29
+ return /* @__PURE__ */ jsxs("section", { "aria-label": "Register counts", children: [
30
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4", children: registers.map((register) => /* @__PURE__ */ jsx(
31
+ StatTile,
32
+ {
33
+ label: REGISTER_LABEL[register],
34
+ value: counts[register] ?? 0
35
+ },
36
+ register
37
+ )) }),
38
+ caption ? /* @__PURE__ */ jsx("p", { className: "mt-4 text-sm text-neutral-500 dark:text-neutral-400", children: caption }) : null
39
+ ] });
40
+ }
41
+ function StatTile({ label, value }) {
42
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900", children: [
43
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-neutral-500 dark:text-neutral-400", children: label }),
44
+ /* @__PURE__ */ jsx("div", { className: "mt-1 text-3xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-50", children: value.toLocaleString() })
45
+ ] });
46
+ }
47
+
48
+ // src/use-counts.ts
49
+ import { useCallback, useEffect, useState } from "react";
50
+ function useCounts(basePath = "/api") {
51
+ const [counts, setCounts] = useState(null);
52
+ const [loading, setLoading] = useState(true);
53
+ const [error, setError] = useState(null);
54
+ const [tick, setTick] = useState(0);
55
+ useEffect(() => {
56
+ let live = true;
57
+ setLoading(true);
58
+ fetch(`${basePath}/counts`).then(async (res) => {
59
+ if (!res.ok) throw new Error(`Counts request failed (${res.status})`);
60
+ return await res.json();
61
+ }).then((body) => {
62
+ if (!live) return;
63
+ setCounts(body.counts);
64
+ setError(null);
65
+ }).catch((e) => {
66
+ if (!live) return;
67
+ setError(e instanceof Error ? e.message : "Failed to load counts");
68
+ }).finally(() => {
69
+ if (live) setLoading(false);
70
+ });
71
+ return () => {
72
+ live = false;
73
+ };
74
+ }, [basePath, tick]);
75
+ const refresh = useCallback(() => setTick((t) => t + 1), []);
76
+ return { counts, loading, error, refresh };
77
+ }
78
+ export {
79
+ REGISTER_LABEL,
80
+ REGISTER_ORDER,
81
+ RegisterCounts,
82
+ useCounts
83
+ };
84
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/labels.ts","../src/register-counts.tsx","../src/use-counts.ts"],"sourcesContent":["import type { Collection } from \"@validation-os/core\";\n\n/** Plain-language labels — the register is a surface a non-technical\n * teammate meets, so no jargon and no code-y plurals. */\nexport const REGISTER_LABEL: Record<Collection, string> = {\n assumptions: \"Assumptions\",\n experiments: \"Experiments\",\n readings: \"Readings\",\n goals: \"Goals\",\n decisions: \"Decisions\",\n glossary: \"Glossary\",\n people: \"People\",\n};\n\n/** The order tiles read left-to-right, top-to-bottom. */\nexport const REGISTER_ORDER: Collection[] = [\n \"assumptions\",\n \"experiments\",\n \"readings\",\n \"goals\",\n \"decisions\",\n \"glossary\",\n \"people\",\n];\n","import type { Collection } from \"@validation-os/core\";\nimport { REGISTER_LABEL, REGISTER_ORDER } from \"./labels.js\";\nimport type { Counts } from \"./use-counts.js\";\n\nexport interface RegisterCountsProps {\n counts: Counts;\n /** Optional caption under the tiles (e.g. the backend the numbers came from). */\n caption?: string;\n}\n\n/**\n * A glanceable grid of stat tiles — one per register, the register's row count\n * as the hero number. Presentational: the caller reads the counts (server-side,\n * through the adapter) and hands them in. Styled with Tailwind utility classes;\n * the host app provides Tailwind.\n */\nexport function RegisterCounts({ counts, caption }: RegisterCountsProps) {\n const registers = REGISTER_ORDER.filter(\n (r): r is Collection => counts[r] !== undefined,\n );\n return (\n <section aria-label=\"Register counts\">\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4\">\n {registers.map((register) => (\n <StatTile\n key={register}\n label={REGISTER_LABEL[register]}\n value={counts[register] ?? 0}\n />\n ))}\n </div>\n {caption ? (\n <p className=\"mt-4 text-sm text-neutral-500 dark:text-neutral-400\">\n {caption}\n </p>\n ) : null}\n </section>\n );\n}\n\nfunction StatTile({ label, value }: { label: string; value: number }) {\n return (\n <div className=\"rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900\">\n <div className=\"text-sm font-medium text-neutral-500 dark:text-neutral-400\">\n {label}\n </div>\n <div className=\"mt-1 text-3xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-50\">\n {value.toLocaleString()}\n </div>\n </div>\n );\n}\n","import { useCallback, useEffect, useState } from \"react\";\nimport type { Collection } from \"@validation-os/core\";\n\nexport type Counts = Partial<Record<Collection, number>>;\n\nexport interface UseCountsResult {\n counts: Counts | null;\n loading: boolean;\n error: string | null;\n refresh: () => void;\n}\n\n/**\n * Client hook: fetch per-register counts from the API (`GET {basePath}/counts`).\n * The API reads Firestore server-side through the adapter; this only speaks\n * HTTP, so no backend credentials ever reach the browser.\n */\nexport function useCounts(basePath = \"/api\"): UseCountsResult {\n const [counts, setCounts] = useState<Counts | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [tick, setTick] = useState(0);\n\n useEffect(() => {\n let live = true;\n setLoading(true);\n fetch(`${basePath}/counts`)\n .then(async (res) => {\n if (!res.ok) throw new Error(`Counts request failed (${res.status})`);\n return (await res.json()) as { counts: Counts };\n })\n .then((body) => {\n if (!live) return;\n setCounts(body.counts);\n setError(null);\n })\n .catch((e: unknown) => {\n if (!live) return;\n setError(e instanceof Error ? e.message : \"Failed to load counts\");\n })\n .finally(() => {\n if (live) setLoading(false);\n });\n return () => {\n live = false;\n };\n }, [basePath, tick]);\n\n const refresh = useCallback(() => setTick((t) => t + 1), []);\n return { counts, loading, error, refresh };\n}\n"],"mappings":";;;AAIO,IAAM,iBAA6C;AAAA,EACxD,aAAa;AAAA,EACb,aAAa;AAAA,EACb,UAAU;AAAA,EACV,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU;AAAA,EACV,QAAQ;AACV;AAGO,IAAM,iBAA+B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;ACFI,SAGM,KAHN;AALG,SAAS,eAAe,EAAE,QAAQ,QAAQ,GAAwB;AACvE,QAAM,YAAY,eAAe;AAAA,IAC/B,CAAC,MAAuB,OAAO,CAAC,MAAM;AAAA,EACxC;AACA,SACE,qBAAC,aAAQ,cAAW,mBAClB;AAAA,wBAAC,SAAI,WAAU,wDACZ,oBAAU,IAAI,CAAC,aACd;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO,eAAe,QAAQ;AAAA,QAC9B,OAAO,OAAO,QAAQ,KAAK;AAAA;AAAA,MAFtB;AAAA,IAGP,CACD,GACH;AAAA,IACC,UACC,oBAAC,OAAE,WAAU,uDACV,mBACH,IACE;AAAA,KACN;AAEJ;AAEA,SAAS,SAAS,EAAE,OAAO,MAAM,GAAqC;AACpE,SACE,qBAAC,SAAI,WAAU,iGACb;AAAA,wBAAC,SAAI,WAAU,8DACZ,iBACH;AAAA,IACA,oBAAC,SAAI,WAAU,kFACZ,gBAAM,eAAe,GACxB;AAAA,KACF;AAEJ;;;ACnDA,SAAS,aAAa,WAAW,gBAAgB;AAiB1C,SAAS,UAAU,WAAW,QAAyB;AAC5D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwB,IAAI;AACxD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,CAAC;AAElC,YAAU,MAAM;AACd,QAAI,OAAO;AACX,eAAW,IAAI;AACf,UAAM,GAAG,QAAQ,SAAS,EACvB,KAAK,OAAO,QAAQ;AACnB,UAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,0BAA0B,IAAI,MAAM,GAAG;AACpE,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB,CAAC,EACA,KAAK,CAAC,SAAS;AACd,UAAI,CAAC,KAAM;AACX,gBAAU,KAAK,MAAM;AACrB,eAAS,IAAI;AAAA,IACf,CAAC,EACA,MAAM,CAAC,MAAe;AACrB,UAAI,CAAC,KAAM;AACX,eAAS,aAAa,QAAQ,EAAE,UAAU,uBAAuB;AAAA,IACnE,CAAC,EACA,QAAQ,MAAM;AACb,UAAI,KAAM,YAAW,KAAK;AAAA,IAC5B,CAAC;AACH,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,UAAU,IAAI,CAAC;AAEnB,QAAM,UAAU,YAAY,MAAM,QAAQ,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3D,SAAO,EAAE,QAAQ,SAAS,OAAO,QAAQ;AAC3C;","names":[]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@validation-os/dashboard",
3
+ "version": "0.1.0",
4
+ "description": "React dashboard components + hooks for validation-os, consuming the DataProvider seam.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "dependencies": {
24
+ "@validation-os/core": "0.1.0"
25
+ },
26
+ "peerDependencies": {
27
+ "react": ">=18",
28
+ "react-dom": ">=18"
29
+ },
30
+ "devDependencies": {
31
+ "@types/react": "^18.3.12",
32
+ "react": "^18.3.1",
33
+ "tsup": "^8.3.0",
34
+ "typescript": "^5.6.3"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/bfish1996/validation-os.git",
39
+ "directory": "packages/dashboard"
40
+ },
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "typecheck": "tsc --noEmit"
44
+ }
45
+ }