@notis_ai/cli 0.2.14 → 0.2.15
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/README.md +82 -20
- package/dist/agent-hooks/notis-agent-hook.mjs +19750 -0
- package/{skills → dist/base-skills}/notis-apps/SKILL.md +84 -53
- package/{skills → dist/base-skills}/notis-cli/SKILL.md +9 -9
- package/dist/base-skills/notis-query/SKILL.md +705 -0
- package/dist/skill-sync/index.js +1528 -0
- package/dist/skill-sync/index.js.map +7 -0
- package/package.json +5 -2
- package/skills/notis-apps/cli.md +38 -17
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +39 -0
- package/skills/notis-onboarding/BRIEF.md +16 -0
- package/src/agent-hook-entry.js +5 -0
- package/src/cli.js +23 -14
- package/src/command-specs/agents.js +392 -0
- package/src/command-specs/apps.js +871 -387
- package/src/command-specs/auth.js +63 -10
- package/src/command-specs/index.js +6 -0
- package/src/command-specs/onboarding.js +69 -5
- package/src/command-specs/skills.js +56 -0
- package/src/runtime/agent-memory-state.js +126 -0
- package/src/runtime/agent-setup.js +383 -0
- package/src/runtime/app-dev-consumers.js +154 -0
- package/src/runtime/app-dev-host-lock.js +80 -0
- package/src/runtime/app-dev-roots.js +284 -0
- package/src/runtime/app-dev-server.js +212 -22
- package/src/runtime/app-dev-sessions.js +105 -145
- package/src/runtime/app-platform.js +1291 -165
- package/src/runtime/app-registry-scaffolds.js +367 -0
- package/src/runtime/base-skills.d.ts +20 -0
- package/src/runtime/base-skills.js +167 -0
- package/src/runtime/login-listener.js +15 -0
- package/src/runtime/oauth.js +1506 -141
- package/src/runtime/output.js +7 -0
- package/src/runtime/ports.js +16 -0
- package/src/runtime/profiles.js +0 -5
- package/src/runtime/skill-sync/cloud-client.ts +96 -0
- package/src/runtime/skill-sync/index.ts +644 -0
- package/src/runtime/skill-sync/local-scanner.ts +1046 -0
- package/src/runtime/skill-sync/symlink-manager.ts +383 -0
- package/src/runtime/skill-sync/sync-plan.ts +22 -0
- package/src/runtime/skill-sync/types.ts +103 -0
- package/src/runtime/skill-sync/write-cloud-skill.ts +50 -0
- package/src/runtime/store-screenshot.js +6 -1
- package/src/runtime/sync-skills.d.ts +37 -0
- package/src/runtime/sync-skills.js +215 -0
- package/template/app/layout.tsx +5 -1
- package/template/notis.config.ts +1 -0
- package/template/packages/sdk/package.json +2 -1
- package/template/packages/sdk/src/components/DocumentEditor.tsx +13 -3
- package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +37 -119
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +5 -1
- package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +1 -1
- package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +55 -0
- package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
- package/template/packages/sdk/src/config.ts +19 -0
- package/template/packages/sdk/src/documents.ts +7 -1
- package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
- package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +709 -0
- package/template/packages/sdk/src/hooks/useHandover.ts +3 -0
- package/template/packages/sdk/src/hooks/useMultiSelect.ts +44 -482
- package/template/packages/sdk/src/hooks/useTool.ts +5 -4
- package/template/packages/sdk/src/index.ts +47 -2
- package/template/packages/sdk/src/interactions/actions.ts +46 -0
- package/template/packages/sdk/src/interactions/shortcuts.tsx +628 -0
- package/template/packages/sdk/src/interactions.ts +41 -0
- package/template/packages/sdk/src/provider.tsx +2 -1
- package/template/packages/sdk/src/runtime.ts +89 -4
- package/dist/scaffolds/notis-database/CHANGELOG.md +0 -5
- package/dist/scaffolds/notis-database/app/globals.css +0 -44
- package/dist/scaffolds/notis-database/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-database/app/page.tsx +0 -1088
- package/dist/scaffolds/notis-database/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-database/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-database/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-database/components/ui/table.tsx +0 -120
- package/dist/scaffolds/notis-database/components.json +0 -20
- package/dist/scaffolds/notis-database/index.html +0 -12
- package/dist/scaffolds/notis-database/lib/types.ts +0 -132
- package/dist/scaffolds/notis-database/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +0 -1839
- package/dist/scaffolds/notis-database/notis.config.ts +0 -73
- package/dist/scaffolds/notis-database/package-lock.json +0 -3935
- package/dist/scaffolds/notis-database/package.json +0 -32
- package/dist/scaffolds/notis-database/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-database/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-database/src/dev-main.tsx +0 -23
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +0 -561
- package/dist/scaffolds/notis-database/tailwind.config.ts +0 -59
- package/dist/scaffolds/notis-database/tsconfig.json +0 -23
- package/dist/scaffolds/notis-database/vite.config.ts +0 -22
- package/dist/scaffolds/notis-journal/CHANGELOG.md +0 -29
- package/dist/scaffolds/notis-journal/app/globals.css +0 -37
- package/dist/scaffolds/notis-journal/app/insights/page.tsx +0 -513
- package/dist/scaffolds/notis-journal/app/journal-core.tsx +0 -362
- package/dist/scaffolds/notis-journal/app/journal-ui.tsx +0 -337
- package/dist/scaffolds/notis-journal/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-journal/app/page.tsx +0 -486
- package/dist/scaffolds/notis-journal/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-journal/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-journal/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-journal/components.json +0 -20
- package/dist/scaffolds/notis-journal/index.html +0 -12
- package/dist/scaffolds/notis-journal/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +0 -132
- package/dist/scaffolds/notis-journal/notis.config.ts +0 -93
- package/dist/scaffolds/notis-journal/package-lock.json +0 -4615
- package/dist/scaffolds/notis-journal/package.json +0 -34
- package/dist/scaffolds/notis-journal/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-journal/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +0 -120
- package/dist/scaffolds/notis-journal/src/dev-main.tsx +0 -58
- package/dist/scaffolds/notis-journal/src/mock-runtime.ts +0 -199
- package/dist/scaffolds/notis-journal/tailwind.config.ts +0 -58
- package/dist/scaffolds/notis-journal/tsconfig.json +0 -23
- package/dist/scaffolds/notis-journal/vite.config.ts +0 -10
- package/dist/scaffolds/notis-notes/CHANGELOG.md +0 -5
- package/dist/scaffolds/notis-notes/app/globals.css +0 -3
- package/dist/scaffolds/notis-notes/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-notes/app/page.tsx +0 -1943
- package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +0 -596
- package/dist/scaffolds/notis-notes/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-notes/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-notes/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-notes/components.json +0 -20
- package/dist/scaffolds/notis-notes/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-notes/lib/visible-properties.ts +0 -144
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +0 -752
- package/dist/scaffolds/notis-notes/notis.config.ts +0 -80
- package/dist/scaffolds/notis-notes/package-lock.json +0 -4636
- package/dist/scaffolds/notis-notes/package.json +0 -35
- package/dist/scaffolds/notis-notes/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-notes/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-notes/tailwind.config.ts +0 -58
- package/dist/scaffolds/notis-notes/tsconfig.json +0 -23
- package/dist/scaffolds/notis-notes/vite.config.ts +0 -10
- package/dist/scaffolds/notis-random/CHANGELOG.md +0 -15
- package/dist/scaffolds/notis-random/README.md +0 -33
- package/dist/scaffolds/notis-random/app/globals.css +0 -11
- package/dist/scaffolds/notis-random/app/history/page.tsx +0 -67
- package/dist/scaffolds/notis-random/app/layout.tsx +0 -7
- package/dist/scaffolds/notis-random/app/page.tsx +0 -289
- package/dist/scaffolds/notis-random/components/ui/button.tsx +0 -50
- package/dist/scaffolds/notis-random/components/ui/card.tsx +0 -16
- package/dist/scaffolds/notis-random/components/ui/input.tsx +0 -23
- package/dist/scaffolds/notis-random/components.json +0 -20
- package/dist/scaffolds/notis-random/index.html +0 -12
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +0 -128
- package/dist/scaffolds/notis-random/lib/rng.ts +0 -202
- package/dist/scaffolds/notis-random/lib/roll-record.ts +0 -189
- package/dist/scaffolds/notis-random/lib/utils.ts +0 -25
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +0 -753
- package/dist/scaffolds/notis-random/notis.config.ts +0 -86
- package/dist/scaffolds/notis-random/package-lock.json +0 -4513
- package/dist/scaffolds/notis-random/package.json +0 -36
- package/dist/scaffolds/notis-random/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-random/postcss.config.mjs +0 -6
- package/dist/scaffolds/notis-random/src/dev-main.tsx +0 -70
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +0 -129
- package/dist/scaffolds/notis-random/tailwind.config.ts +0 -50
- package/dist/scaffolds/notis-random/tsconfig.json +0 -23
- package/dist/scaffolds/notis-random/vite.config.ts +0 -11
- package/dist/scaffolds.json +0 -49
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
-
import { useTool } from '@notis/sdk';
|
|
5
|
-
import type { RollDocument } from '@/lib/roll-record';
|
|
6
|
-
import type { Roll } from '@/lib/rng';
|
|
7
|
-
import { formatNumber } from '@/lib/utils';
|
|
8
|
-
|
|
9
|
-
type QueryRollsArgs = {
|
|
10
|
-
database_slug: 'rolls';
|
|
11
|
-
query?: {
|
|
12
|
-
page_size?: number;
|
|
13
|
-
};
|
|
14
|
-
offset?: number;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type QueryRollsResult = {
|
|
18
|
-
documents?: RollDocument[];
|
|
19
|
-
has_more?: boolean;
|
|
20
|
-
next_offset?: number | null;
|
|
21
|
-
message?: string;
|
|
22
|
-
error?: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
type UpsertRollArgs = {
|
|
26
|
-
title: string;
|
|
27
|
-
Value: number;
|
|
28
|
-
Mode: Roll['mode'];
|
|
29
|
-
Min: number;
|
|
30
|
-
Max: number;
|
|
31
|
-
'Rolled At': string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type UpsertRollResult = {
|
|
35
|
-
status?: string;
|
|
36
|
-
document?: RollDocument;
|
|
37
|
-
message?: string;
|
|
38
|
-
error?: string;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export function useRollDocuments(pageSize = 20, fetchAll = false) {
|
|
42
|
-
const queryRolls = useTool<QueryRollsArgs, QueryRollsResult>('LOCAL_NOTIS_DATABASE_QUERY');
|
|
43
|
-
const [documents, setDocuments] = useState<RollDocument[]>([]);
|
|
44
|
-
const [error, setError] = useState<Error | null>(null);
|
|
45
|
-
const [fetchKey, setFetchKey] = useState(0);
|
|
46
|
-
|
|
47
|
-
const refetch = useCallback(() => {
|
|
48
|
-
setFetchKey((key) => key + 1);
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
let cancelled = false;
|
|
53
|
-
setError(null);
|
|
54
|
-
|
|
55
|
-
const fetchRolls = async (): Promise<RollDocument[]> => {
|
|
56
|
-
const allDocuments: RollDocument[] = [];
|
|
57
|
-
let offset = 0;
|
|
58
|
-
while (true) {
|
|
59
|
-
const result = await queryRolls.call({
|
|
60
|
-
database_slug: 'rolls',
|
|
61
|
-
query: { page_size: pageSize },
|
|
62
|
-
...(offset > 0 ? { offset } : {}),
|
|
63
|
-
});
|
|
64
|
-
const message = result.error ?? result.message;
|
|
65
|
-
if (message && !result.documents) {
|
|
66
|
-
throw new Error(message);
|
|
67
|
-
}
|
|
68
|
-
allDocuments.push(...(result.documents ?? []));
|
|
69
|
-
if (!fetchAll || !result.has_more) return allDocuments;
|
|
70
|
-
if (typeof result.next_offset !== 'number' || result.next_offset <= offset) {
|
|
71
|
-
throw new Error('Roll history returned an invalid pagination offset.');
|
|
72
|
-
}
|
|
73
|
-
offset = result.next_offset;
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
fetchRolls()
|
|
78
|
-
.then((result) => {
|
|
79
|
-
if (cancelled) return;
|
|
80
|
-
setDocuments(result);
|
|
81
|
-
})
|
|
82
|
-
.catch((err) => {
|
|
83
|
-
if (cancelled) return;
|
|
84
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
85
|
-
setDocuments([]);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
return () => {
|
|
89
|
-
cancelled = true;
|
|
90
|
-
};
|
|
91
|
-
}, [fetchAll, fetchKey, pageSize, queryRolls.call]);
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
documents,
|
|
95
|
-
loading: queryRolls.loading,
|
|
96
|
-
error: queryRolls.error ?? error,
|
|
97
|
-
refetch,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function usePersistRoll() {
|
|
102
|
-
const upsertRoll = useTool<UpsertRollArgs, UpsertRollResult>('LOCAL_NOTIS_DATABASE_UPSERT_ROLLS');
|
|
103
|
-
|
|
104
|
-
const persist = useCallback(
|
|
105
|
-
async (roll: Roll) => {
|
|
106
|
-
const result = await upsertRoll.call({
|
|
107
|
-
title: formatNumber(roll.value),
|
|
108
|
-
Value: roll.value,
|
|
109
|
-
Mode: roll.mode,
|
|
110
|
-
Min: roll.min,
|
|
111
|
-
Max: roll.max,
|
|
112
|
-
'Rolled At': roll.at,
|
|
113
|
-
});
|
|
114
|
-
const message = result.error ?? result.message;
|
|
115
|
-
if (message && result.status === 'error') {
|
|
116
|
-
throw new Error(message);
|
|
117
|
-
}
|
|
118
|
-
return result.document ?? null;
|
|
119
|
-
},
|
|
120
|
-
[upsertRoll.call],
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
persist,
|
|
125
|
-
loading: upsertRoll.loading,
|
|
126
|
-
error: upsertRoll.error,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
export type Mode = 'integer' | 'decimal' | 'dice';
|
|
2
|
-
|
|
3
|
-
export interface Roll {
|
|
4
|
-
value: number;
|
|
5
|
-
mode: Mode;
|
|
6
|
-
min: number;
|
|
7
|
-
max: number;
|
|
8
|
-
at: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** Standard polyhedral dice offered in dice mode. */
|
|
12
|
-
export const DICE_SIDES = [4, 6, 8, 10, 12, 20] as const;
|
|
13
|
-
export type DiceSides = (typeof DICE_SIDES)[number];
|
|
14
|
-
export const DEFAULT_DICE_SIDES: DiceSides = 6;
|
|
15
|
-
|
|
16
|
-
/** Largest span we accept for an integer draw (stays inside Number.MAX_SAFE_INTEGER). */
|
|
17
|
-
export const MAX_INTEGER_SPAN = 2 ** 32;
|
|
18
|
-
|
|
19
|
-
const UINT32_RANGE = 0x1_0000_0000; // 2 ** 32
|
|
20
|
-
|
|
21
|
-
/** Convert an editable numeric draft without treating an empty field as zero. */
|
|
22
|
-
export function parseBoundDraft(value: string): number {
|
|
23
|
-
return value.trim() === '' ? Number.NaN : Number(value);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function randomUint32(): number {
|
|
27
|
-
const array = new Uint32Array(1);
|
|
28
|
-
crypto.getRandomValues(array);
|
|
29
|
-
return array[0] ?? 0;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Uniform float in [0, 1) — note the exclusive upper bound. Dividing by 2**32
|
|
34
|
-
* (not 2**32 - 1) is what keeps 1.0 unreachable, so callers can safely scale
|
|
35
|
-
* the result without ever landing one past the top of their range.
|
|
36
|
-
*/
|
|
37
|
-
export function randomUnit(): number {
|
|
38
|
-
return randomUint32() / UINT32_RANGE;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Uniform integer in [lo, hi], both inclusive.
|
|
43
|
-
*
|
|
44
|
-
* `Math.floor(randomUnit() * span)` alone is biased whenever `span` does not
|
|
45
|
-
* divide 2**32 evenly: the first `2**32 % span` buckets would each get one
|
|
46
|
-
* extra source value. We remove that by rejecting draws that fall in the
|
|
47
|
-
* ragged tail of the uint32 space, which makes every bucket exactly the same
|
|
48
|
-
* size. The expected number of retries is below 1 for any span we allow.
|
|
49
|
-
*/
|
|
50
|
-
export function randomIntInclusive(lo: number, hi: number): number {
|
|
51
|
-
const low = Math.ceil(lo);
|
|
52
|
-
const high = Math.floor(hi);
|
|
53
|
-
if (!Number.isFinite(low) || !Number.isFinite(high) || high < low) {
|
|
54
|
-
throw new RangeError(`Invalid integer range [${lo}, ${hi}]`);
|
|
55
|
-
}
|
|
56
|
-
if (!Number.isSafeInteger(low) || !Number.isSafeInteger(high)) {
|
|
57
|
-
throw new RangeError(`Integer bounds must be safe integers: [${lo}, ${hi}]`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const span = high - low + 1;
|
|
61
|
-
if (span <= 1) return low;
|
|
62
|
-
if (span > MAX_INTEGER_SPAN) {
|
|
63
|
-
throw new RangeError(`Integer range too wide: ${span} values`);
|
|
64
|
-
}
|
|
65
|
-
if (span === UINT32_RANGE) {
|
|
66
|
-
return low + randomUint32();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Values at or above `limit` fall outside the last whole bucket; redraw them.
|
|
70
|
-
const limit = UINT32_RANGE - (UINT32_RANGE % span);
|
|
71
|
-
let draw = randomUint32();
|
|
72
|
-
while (draw >= limit) {
|
|
73
|
-
draw = randomUint32();
|
|
74
|
-
}
|
|
75
|
-
return low + (draw % span);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Uniform float in [lo, hi) — inclusive low, exclusive high. */
|
|
79
|
-
export function randomFloat(lo: number, hi: number): number {
|
|
80
|
-
if (hi === lo) return lo;
|
|
81
|
-
const span = hi - lo;
|
|
82
|
-
if (!Number.isFinite(lo) || !Number.isFinite(hi) || !Number.isFinite(span) || span < 0) {
|
|
83
|
-
throw new RangeError(`Invalid decimal range [${lo}, ${hi}]`);
|
|
84
|
-
}
|
|
85
|
-
return lo + randomUnit() * span;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export type BoundsIssue =
|
|
89
|
-
| { code: 'min-not-a-number'; message: string }
|
|
90
|
-
| { code: 'max-not-a-number'; message: string }
|
|
91
|
-
| { code: 'min-not-finite'; message: string }
|
|
92
|
-
| { code: 'max-not-finite'; message: string }
|
|
93
|
-
| { code: 'min-not-below-max'; message: string }
|
|
94
|
-
| { code: 'empty-integer-range'; message: string }
|
|
95
|
-
| { code: 'range-too-wide'; message: string };
|
|
96
|
-
|
|
97
|
-
export interface BoundsCheck {
|
|
98
|
-
ok: boolean;
|
|
99
|
-
issue: BoundsIssue | null;
|
|
100
|
-
/** Bounds actually used by `roll`, after integer snapping. Null when invalid. */
|
|
101
|
-
min: number | null;
|
|
102
|
-
max: number | null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Validates a min/max pair for a given mode and reports the bounds that would
|
|
107
|
-
* actually be used. Integer and dice modes snap inward (ceil the low end, floor
|
|
108
|
-
* the high end) so a range like [1.2, 4.8] draws from {2, 3, 4} rather than
|
|
109
|
-
* producing fractional "integers".
|
|
110
|
-
*/
|
|
111
|
-
export function checkBounds(min: number, max: number, mode: Mode): BoundsCheck {
|
|
112
|
-
const invalid = (issue: BoundsIssue): BoundsCheck => ({ ok: false, issue, min: null, max: null });
|
|
113
|
-
|
|
114
|
-
if (Number.isNaN(min)) {
|
|
115
|
-
return invalid({ code: 'min-not-a-number', message: 'Minimum must be a number.' });
|
|
116
|
-
}
|
|
117
|
-
if (Number.isNaN(max)) {
|
|
118
|
-
return invalid({ code: 'max-not-a-number', message: 'Maximum must be a number.' });
|
|
119
|
-
}
|
|
120
|
-
if (!Number.isFinite(min)) {
|
|
121
|
-
return invalid({ code: 'min-not-finite', message: 'Minimum must be finite.' });
|
|
122
|
-
}
|
|
123
|
-
if (!Number.isFinite(max)) {
|
|
124
|
-
return invalid({ code: 'max-not-finite', message: 'Maximum must be finite.' });
|
|
125
|
-
}
|
|
126
|
-
if (min >= max) {
|
|
127
|
-
return invalid({
|
|
128
|
-
code: 'min-not-below-max',
|
|
129
|
-
message: 'Minimum must be strictly lower than maximum.',
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (mode === 'decimal') {
|
|
134
|
-
if (!Number.isFinite(max - min)) {
|
|
135
|
-
return invalid({
|
|
136
|
-
code: 'range-too-wide',
|
|
137
|
-
message: 'Range is too wide for a decimal draw.',
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
return { ok: true, issue: null, min, max };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const low = Math.ceil(min);
|
|
144
|
-
const high = Math.floor(max);
|
|
145
|
-
if (high < low) {
|
|
146
|
-
return invalid({
|
|
147
|
-
code: 'empty-integer-range',
|
|
148
|
-
message: `No whole number sits between ${min} and ${max}.`,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
if (!Number.isSafeInteger(low) || !Number.isSafeInteger(high)) {
|
|
152
|
-
return invalid({
|
|
153
|
-
code: 'range-too-wide',
|
|
154
|
-
message: 'Whole-number bounds must be safe integers.',
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
if (high - low + 1 > MAX_INTEGER_SPAN) {
|
|
158
|
-
return invalid({
|
|
159
|
-
code: 'range-too-wide',
|
|
160
|
-
message: 'Range is too wide for a whole-number draw.',
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
return { ok: true, issue: null, min: low, max: high };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Draws one value.
|
|
168
|
-
*
|
|
169
|
-
* - `integer` / `dice`: uniform over the whole numbers in [min, max], both ends
|
|
170
|
-
* inclusive and equally likely.
|
|
171
|
-
* - `decimal`: uniform over the continuous interval [min, max) — the low bound
|
|
172
|
-
* is reachable, the high bound is not.
|
|
173
|
-
*
|
|
174
|
-
* Throws a `RangeError` when the bounds are unusable; call `checkBounds` first
|
|
175
|
-
* to surface the reason to the user.
|
|
176
|
-
*/
|
|
177
|
-
export function roll(min: number, max: number, mode: Mode): Roll {
|
|
178
|
-
const check = checkBounds(min, max, mode);
|
|
179
|
-
if (!check.ok || check.min == null || check.max == null) {
|
|
180
|
-
throw new RangeError(check.issue?.message ?? 'Invalid range.');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const value =
|
|
184
|
-
mode === 'decimal'
|
|
185
|
-
? randomFloat(check.min, check.max)
|
|
186
|
-
: randomIntInclusive(check.min, check.max);
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
value,
|
|
190
|
-
mode,
|
|
191
|
-
min: check.min,
|
|
192
|
-
max: check.max,
|
|
193
|
-
at: new Date().toISOString(),
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** Human label for a mode, matching the `Mode` select options in the database. */
|
|
198
|
-
export function modeLabel(mode: Mode): string {
|
|
199
|
-
if (mode === 'decimal') return 'Decimal';
|
|
200
|
-
if (mode === 'dice') return 'Dice';
|
|
201
|
-
return 'Integer';
|
|
202
|
-
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import type { Mode } from '@/lib/rng';
|
|
2
|
-
|
|
3
|
-
export interface RollDocument {
|
|
4
|
-
id: string;
|
|
5
|
-
title?: string;
|
|
6
|
-
properties?: Record<string, unknown>;
|
|
7
|
-
createdAt?: string | null;
|
|
8
|
-
created_at?: string | null;
|
|
9
|
-
created_time?: string | null;
|
|
10
|
-
createdTime?: string | null;
|
|
11
|
-
lastEditedTime?: string | null;
|
|
12
|
-
last_edited_time?: string | null;
|
|
13
|
-
updated_at?: string | null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
17
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
18
|
-
? (value as Record<string, unknown>)
|
|
19
|
-
: null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Flattens a Notion-shaped rich_text / title array to plain text. */
|
|
23
|
-
function flattenRichText(value: unknown): string {
|
|
24
|
-
if (typeof value === 'string') return value;
|
|
25
|
-
if (!Array.isArray(value)) return '';
|
|
26
|
-
return value
|
|
27
|
-
.map((item) => {
|
|
28
|
-
const record = asRecord(item);
|
|
29
|
-
const text = asRecord(record?.text);
|
|
30
|
-
if (typeof text?.content === 'string') return text.content;
|
|
31
|
-
if (typeof record?.plain_text === 'string') return record.plain_text;
|
|
32
|
-
return '';
|
|
33
|
-
})
|
|
34
|
-
.join('');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Collapses a Notion-shaped property value (`{type: 'number', number: 42}`)
|
|
39
|
-
* to the underlying primitive. The native database tools return this shape,
|
|
40
|
-
* while fixtures and older rows may already be flat, so both are accepted and
|
|
41
|
-
* anything unrecognised passes straight through.
|
|
42
|
-
*/
|
|
43
|
-
export function unwrapProperty(value: unknown): unknown {
|
|
44
|
-
const record = asRecord(value);
|
|
45
|
-
if (!record) return value;
|
|
46
|
-
|
|
47
|
-
const type = typeof record.type === 'string' ? record.type : null;
|
|
48
|
-
if (!type) return value;
|
|
49
|
-
|
|
50
|
-
if (type === 'title') return flattenRichText(record.title);
|
|
51
|
-
if (type === 'rich_text') return flattenRichText(record.rich_text);
|
|
52
|
-
if (type === 'select' || type === 'status') {
|
|
53
|
-
const inner = asRecord(record[type]);
|
|
54
|
-
return typeof inner?.name === 'string' ? inner.name : (record[type] ?? null);
|
|
55
|
-
}
|
|
56
|
-
if (type === 'date') {
|
|
57
|
-
const inner = asRecord(record.date);
|
|
58
|
-
return typeof inner?.start === 'string' ? inner.start : (record.date ?? null);
|
|
59
|
-
}
|
|
60
|
-
if (type in record) return record[type];
|
|
61
|
-
return value;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function readString(value: unknown): string | null {
|
|
65
|
-
const unwrapped = unwrapProperty(value);
|
|
66
|
-
if (typeof unwrapped === 'string') {
|
|
67
|
-
const trimmed = unwrapped.trim();
|
|
68
|
-
return trimmed.length ? trimmed : null;
|
|
69
|
-
}
|
|
70
|
-
if (typeof unwrapped === 'number' || typeof unwrapped === 'boolean') {
|
|
71
|
-
return String(unwrapped);
|
|
72
|
-
}
|
|
73
|
-
const record = asRecord(unwrapped);
|
|
74
|
-
if (record) {
|
|
75
|
-
if (typeof record.name === 'string') return record.name;
|
|
76
|
-
if (typeof record.start === 'string') return record.start;
|
|
77
|
-
}
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function readNumber(value: unknown): number | null {
|
|
82
|
-
const unwrapped = unwrapProperty(value);
|
|
83
|
-
if (typeof unwrapped === 'number' && Number.isFinite(unwrapped)) {
|
|
84
|
-
return unwrapped;
|
|
85
|
-
}
|
|
86
|
-
const stringValue = readString(unwrapped);
|
|
87
|
-
if (!stringValue) {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
const parsed = Number(stringValue);
|
|
91
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function readMode(value: unknown): Mode {
|
|
95
|
-
const raw = readString(value)?.toLowerCase();
|
|
96
|
-
if (raw === 'integer' || raw === 'decimal' || raw === 'dice') {
|
|
97
|
-
return raw;
|
|
98
|
-
}
|
|
99
|
-
return 'integer';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function readTimestamp(doc: RollDocument): string {
|
|
103
|
-
return (
|
|
104
|
-
readString(doc.properties?.['Rolled At']) ||
|
|
105
|
-
doc.createdAt ||
|
|
106
|
-
doc.created_at ||
|
|
107
|
-
doc.created_time ||
|
|
108
|
-
doc.createdTime ||
|
|
109
|
-
doc.lastEditedTime ||
|
|
110
|
-
doc.last_edited_time ||
|
|
111
|
-
doc.updated_at ||
|
|
112
|
-
''
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function sortTimestamp(value: string): number {
|
|
117
|
-
if (!value) return 0;
|
|
118
|
-
const parsed = Date.parse(value);
|
|
119
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export interface RollRecord {
|
|
123
|
-
id: string;
|
|
124
|
-
value: number;
|
|
125
|
-
mode: Mode;
|
|
126
|
-
min: number | null;
|
|
127
|
-
max: number | null;
|
|
128
|
-
at: string;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function normalizeRollRecord(doc: RollDocument): RollRecord {
|
|
132
|
-
const value =
|
|
133
|
-
readNumber(doc.properties?.['Value']) ??
|
|
134
|
-
readNumber(doc.properties?.['Name']) ??
|
|
135
|
-
readNumber(doc.title) ??
|
|
136
|
-
0;
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
id: doc.id,
|
|
140
|
-
value,
|
|
141
|
-
mode: readMode(doc.properties?.['Mode']),
|
|
142
|
-
min: readNumber(doc.properties?.['Min']),
|
|
143
|
-
max: readNumber(doc.properties?.['Max']),
|
|
144
|
-
at: readTimestamp(doc),
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function sortRollRecordsDesc(a: RollRecord, b: RollRecord): number {
|
|
149
|
-
return sortTimestamp(b.at) - sortTimestamp(a.at);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Merges optimistic local rolls with fetched rows, newest first, id-deduped. */
|
|
153
|
-
export function mergeRollRecords(...groups: RollRecord[][]): RollRecord[] {
|
|
154
|
-
const byId = new Map<string, RollRecord>();
|
|
155
|
-
for (const group of groups) {
|
|
156
|
-
for (const record of group) {
|
|
157
|
-
if (!byId.has(record.id)) {
|
|
158
|
-
byId.set(record.id, record);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return Array.from(byId.values()).sort(sortRollRecordsDesc);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export interface RollStats {
|
|
166
|
-
count: number;
|
|
167
|
-
lowest: number;
|
|
168
|
-
highest: number;
|
|
169
|
-
average: number;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** Summary of a set of rolls. Returns null for an empty set. */
|
|
173
|
-
export function summarizeRolls(records: RollRecord[]): RollStats | null {
|
|
174
|
-
if (records.length === 0) return null;
|
|
175
|
-
let lowest = Number.POSITIVE_INFINITY;
|
|
176
|
-
let highest = Number.NEGATIVE_INFINITY;
|
|
177
|
-
let total = 0;
|
|
178
|
-
for (const record of records) {
|
|
179
|
-
if (record.value < lowest) lowest = record.value;
|
|
180
|
-
if (record.value > highest) highest = record.value;
|
|
181
|
-
total += record.value;
|
|
182
|
-
}
|
|
183
|
-
return {
|
|
184
|
-
count: records.length,
|
|
185
|
-
lowest,
|
|
186
|
-
highest,
|
|
187
|
-
average: total / records.length,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { clsx, type ClassValue } from 'clsx';
|
|
2
|
-
import { twMerge } from 'tailwind-merge';
|
|
3
|
-
|
|
4
|
-
export function cn(...inputs: ClassValue[]): string {
|
|
5
|
-
return twMerge(clsx(inputs));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function formatNumber(value: number): string {
|
|
9
|
-
if (Number.isInteger(value)) return value.toString();
|
|
10
|
-
return value.toFixed(4).replace(/0+$/, '').replace(/\.$/, '');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function relativeTime(iso: string | null | undefined): string {
|
|
14
|
-
if (!iso) return '';
|
|
15
|
-
const then = new Date(iso).getTime();
|
|
16
|
-
if (Number.isNaN(then)) return '';
|
|
17
|
-
const diffSeconds = Math.round((Date.now() - then) / 1000);
|
|
18
|
-
if (diffSeconds < 5) return 'just now';
|
|
19
|
-
if (diffSeconds < 60) return `${diffSeconds}s ago`;
|
|
20
|
-
const diffMinutes = Math.round(diffSeconds / 60);
|
|
21
|
-
if (diffMinutes < 60) return `${diffMinutes}m ago`;
|
|
22
|
-
const diffHours = Math.round(diffMinutes / 60);
|
|
23
|
-
if (diffHours < 24) return `${diffHours}h ago`;
|
|
24
|
-
return new Date(iso).toLocaleDateString();
|
|
25
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|