@heyramzi/ui 2.22.1 → 2.24.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/COMPONENTS.md +3 -6
- package/dist/ai/confirmation/index.js +3 -6
- package/dist/ai/index.js +1 -5
- package/dist/canvas/editor/rebase.js +26 -2
- package/dist/charts/dashboard/index.js +1 -3
- package/dist/charts/primitives/chart-utils.js +14 -0
- package/dist/components/chip/index.js +1 -0
- package/dist/components/language-flag/flags.js +5 -0
- package/dist/components/language-flag/index.js +1 -0
- package/dist/components/list-page/index.js +1 -3
- package/dist/components/work-parts/index.js +1 -1
- package/dist/editor/doc-blocks.js +2 -1
- package/dist/editor/safe-url.js +26 -0
- package/dist/editor/serializer.js +10 -4
- package/dist/editor/slash-query.js +10 -0
- package/dist/effects/animated-beam/path-calculator.svelte.js +13 -6
- package/dist/lib/dates.js +41 -0
- package/dist/lib/panel.js +2 -2
- package/dist/lib/tone.js +13 -0
- package/dist/record/format.js +2 -18
- package/dist/record/logic.js +3 -6
- package/dist/status/index.js +1 -1
- package/dist/triage/index.js +4 -0
- package/dist/triage/types.js +12 -0
- package/dist/work/dates.js +2 -3
- package/dist/work/detail/detail-types.js +0 -8
- package/dist/work/docs/edited.js +8 -7
- package/dist/work/inbox/inbox-logic.js +5 -20
- package/dist/work/index.js +0 -2
- package/dist/work/time/duration.js +2 -3
- package/package.json +7 -21
- package/scripts/sync-shadcn.mjs +4 -6
- package/src/ai/confirmation/Confirmation.svelte +2 -2
- package/src/ai/confirmation/{ConfirmationRequest.svelte → ConfirmationState.svelte} +6 -3
- package/src/ai/confirmation/confirmation-context.svelte.ts +2 -2
- package/src/ai/confirmation/confirmation-parts.test.svelte +17 -0
- package/src/ai/confirmation/confirmation-parts.test.ts +23 -0
- package/src/ai/confirmation/index.ts +6 -21
- package/src/ai/index.ts +3 -13
- package/src/ai/typing-dots/TypingDots.svelte +32 -12
- package/src/ai/typing-dots/typing-dots.test.ts +36 -0
- package/src/animations/modal.css +4 -1
- package/src/canvas/editor/rebase.test.ts +37 -0
- package/src/canvas/editor/rebase.ts +33 -1
- package/src/canvas/ui/canvas-ui.css +44 -1
- package/src/charts/README.md +2 -2
- package/src/charts/area-chart.svelte +5 -12
- package/src/charts/bar-chart.svelte +6 -14
- package/src/charts/chart-tokens.css +19 -0
- package/src/charts/dashboard/data-table.svelte +21 -21
- package/src/charts/dashboard/index.ts +1 -3
- package/src/charts/dashboard/rating-chip.svelte +9 -11
- package/src/charts/dashboard/stat-tile.svelte +4 -5
- package/src/charts/donut-chart.svelte +1 -1
- package/src/charts/gauge-chart.svelte +1 -1
- package/src/charts/line-chart.svelte +5 -12
- package/src/charts/number-chart.svelte +1 -1
- package/src/charts/primitives/chart-utils.test.ts +17 -0
- package/src/charts/primitives/chart-utils.ts +16 -0
- package/src/charts/ring-chart.svelte +2 -2
- package/src/charts/sparkline.svelte +1 -1
- package/src/charts/stat-card.svelte +1 -1
- package/src/charts/time-series-chart.svelte +9 -5
- package/src/components/chip/count-chip.svelte +35 -0
- package/src/components/chip/index.ts +1 -0
- package/src/components/data-table/data-table.ts +1 -1
- package/src/components/data-table/row-context-menu.svelte +3 -4
- package/src/components/data-table/sortable-head.svelte +45 -10
- package/src/components/language-flag/flags.ts +5 -0
- package/src/components/language-flag/index.ts +1 -0
- package/src/components/language-flag/language-flag.svelte +3 -11
- package/src/components/list-page/empty-state.svelte +90 -12
- package/src/components/list-page/filter-popover.svelte +2 -5
- package/src/components/list-page/grouped-list.svelte +2 -4
- package/src/components/list-page/index.ts +1 -3
- package/src/components/section-header/section-header.svelte +2 -6
- package/src/components/segmented/segment.svelte +13 -19
- package/src/components/segmented/segmented-filter.svelte +1 -2
- package/src/components/table-view/table-view.svelte +31 -45
- package/src/components/table-view/table-view.test.ts +6 -3
- package/src/components/work-parts/group-head.svelte +1 -1
- package/src/components/work-parts/index.ts +1 -1
- package/src/editor/doc-blocks.ts +2 -1
- package/src/editor/editor.svelte +7 -5
- package/src/editor/safe-url.ts +32 -0
- package/src/editor/serializer.test.ts +58 -0
- package/src/editor/serializer.ts +9 -6
- package/src/editor/slash-query.test.ts +19 -0
- package/src/editor/slash-query.ts +11 -0
- package/src/effects/animated-beam/path-calculator.svelte.ts +19 -5
- package/src/effects/marquee/Marquee.css +6 -3
- package/src/effects/marquee/Marquee.svelte +6 -2
- package/src/inbox/thread-inbox.svelte +6 -4
- package/src/lib/dates.test.ts +63 -0
- package/src/lib/dates.ts +63 -0
- package/src/lib/panel.svelte +116 -0
- package/src/lib/panel.ts +2 -2
- package/src/lib/tone.ts +15 -0
- package/src/modules.test.ts +13 -1
- package/src/panel/side-panel.svelte +4 -4
- package/src/record/format.ts +3 -15
- package/src/record/logic.ts +2 -6
- package/src/record/record-stats.svelte +4 -3
- package/src/record/steps-rail.svelte +7 -4
- package/src/shadcn/alert-dialog/alert-dialog-overlay.svelte +1 -1
- package/src/shadcn/button/button.svelte +1 -1
- package/src/shadcn/button/button.test.ts +2 -2
- package/src/shadcn/command/command-dialog.svelte +5 -10
- package/src/shadcn/dialog/dialog-overlay.svelte +1 -1
- package/src/shadcn/input/input.svelte +2 -2
- package/src/shadcn/sheet/sheet-overlay.svelte +1 -1
- package/src/status/index.ts +1 -1
- package/src/status/status-pill.svelte +2 -8
- package/src/thread/thread.svelte +3 -6
- package/src/tokens/craft.css +30 -0
- package/src/triage/README.md +28 -0
- package/src/triage/index.ts +10 -0
- package/src/triage/triage-plan.svelte +132 -0
- package/src/triage/triage-queue.svelte +48 -0
- package/src/triage/triage-task.svelte +95 -0
- package/src/triage/types.ts +35 -0
- package/src/work/README.md +0 -1
- package/src/work/dates.ts +3 -3
- package/src/work/demo.svelte +9 -1
- package/src/work/detail/detail-demo.svelte +30 -27
- package/src/work/detail/detail-types.ts +1 -29
- package/src/work/detail/task-detail.svelte +18 -17
- package/src/work/docs/doc-menu.svelte +2 -1
- package/src/work/docs/edited.ts +4 -7
- package/src/work/inbox/inbox-card.svelte +4 -9
- package/src/work/inbox/inbox-logic.ts +4 -16
- package/src/work/index.ts +1 -3
- package/src/work/kanban-column.svelte +5 -3
- package/src/work/time/duration.ts +2 -3
- package/src/work/time/time-chip.svelte +1 -3
- package/dist/ai/thinking-dots/index.js +0 -3
- package/dist/components/icon/index.js +0 -1
- package/dist/components/locale-switch/index.js +0 -1
- package/dist/icons/trades.js +0 -49
- package/dist/status/pill-tint.js +0 -13
- package/dist/work/presets/sales.js +0 -39
- package/src/ai/confirmation/ConfirmationAccepted.svelte +0 -23
- package/src/ai/confirmation/ConfirmationFailed.svelte +0 -23
- package/src/ai/confirmation/ConfirmationRejected.svelte +0 -23
- package/src/ai/thinking-dots/ThinkingDots.svelte +0 -42
- package/src/ai/thinking-dots/index.ts +0 -5
- package/src/charts/dashboard/dashboard-empty.svelte +0 -55
- package/src/charts/dashboard/data-panel.svelte +0 -81
- package/src/charts/dashboard/data-sort-head.svelte +0 -102
- package/src/components/icon/icon.svelte +0 -23
- package/src/components/icon/index.ts +0 -1
- package/src/components/list-page/detail-empty.svelte +0 -45
- package/src/components/list-page/detail-panel.svelte +0 -82
- package/src/components/list-page/rail-empty.svelte +0 -44
- package/src/components/locale-switch/index.ts +0 -1
- package/src/components/locale-switch/locale-switch.svelte +0 -59
- package/src/components/work-parts/count-chip.svelte +0 -20
- package/src/icons/TradeIcon.svelte +0 -16
- package/src/icons/trades.ts +0 -89
- package/src/status/pill-tint.ts +0 -16
- package/src/work/detail/task-property.svelte +0 -17
- package/src/work/detail/task-thread.svelte +0 -221
- package/src/work/presets/sales.ts +0 -16
package/COMPONENTS.md
CHANGED
|
@@ -64,11 +64,9 @@ Generic pieces built on the primitives, imported from `@heyramzi/ui/components/<
|
|
|
64
64
|
- `copy` — CopyButton, copyText, copyRichText, htmlToPlainText, Copied
|
|
65
65
|
- `data-table` — SortableHead, Pagination, InfiniteScroll, RowContextMenu, TableSkeleton, and the sort, page and filter helpers
|
|
66
66
|
- `hold-to-confirm` — HoldToConfirm, press and hold to confirm
|
|
67
|
-
- `
|
|
68
|
-
- `language-flag` — LanguageFlag
|
|
67
|
+
- `language-flag` — LanguageFlag, LANGUAGE_FLAGS (code to `/flags/*.svg`, also a SegmentedFilter option's `image`)
|
|
69
68
|
- `list-page` — ListPageShell, MasterDetail, GroupedList, RailRow and the rest of the master-detail page kit, NoticeBar, EmptyState, FilterPopover, setSectionHost
|
|
70
69
|
- `toolbar` — Toolbar: one row at any width; search, tabs and the view switch fold into menus in a narrow pane
|
|
71
|
-
- `locale-switch` — LocaleSwitch, a language switch on SegmentedFilter
|
|
72
70
|
- `mark` — Mark, a logo or photo glyph with an initial fallback
|
|
73
71
|
- `open-in` — OpenIn, a link out to a tool that takes the tool's name, label and mark as a prop
|
|
74
72
|
- `picker` — Picker, MarkPicker, pickerTriggerClass, a command palette at popover scale
|
|
@@ -122,15 +120,14 @@ Generic pieces built on the primitives, imported from `@heyramzi/ui/components/<
|
|
|
122
120
|
## AI and chat (`src/ai`)
|
|
123
121
|
|
|
124
122
|
- `ai-button` — AiButton
|
|
125
|
-
- `confirmation` — Confirmation, ConfirmationTitle,
|
|
123
|
+
- `confirmation` — Confirmation, ConfirmationTitle, ConfirmationState (`when` names the kind it shows), ConfirmationActions, ConfirmationAction, type ConfirmationKind
|
|
126
124
|
- `generation-progress` — GenerationProgress
|
|
127
125
|
- `loading-state` — PixelLoader
|
|
128
126
|
- `prompt-input` — PromptInput, PromptInputBody, PromptInputToolbar, PromptInputTextarea, PromptInputSubmit
|
|
129
127
|
- `reasoning` — Reasoning, ReasoningTrigger, ReasoningContent
|
|
130
128
|
- `response` — Response
|
|
131
129
|
- `suggestion` — Suggestion, Suggestions
|
|
132
|
-
- `
|
|
133
|
-
- `typing-dots` — TypingDots
|
|
130
|
+
- `typing-dots` — TypingDots. A `label` makes it a `role="status"` line
|
|
134
131
|
- `voice-waveform` — VoiceWaveform
|
|
135
132
|
|
|
136
133
|
## Record (`src/record`)
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import Confirmation from "../../../src/ai/confirmation/Confirmation.svelte";
|
|
2
2
|
import ConfirmationTitle from "../../../src/ai/confirmation/ConfirmationTitle.svelte";
|
|
3
|
-
import
|
|
4
|
-
import ConfirmationAccepted from "../../../src/ai/confirmation/ConfirmationAccepted.svelte";
|
|
5
|
-
import ConfirmationRejected from "../../../src/ai/confirmation/ConfirmationRejected.svelte";
|
|
6
|
-
import ConfirmationFailed from "../../../src/ai/confirmation/ConfirmationFailed.svelte";
|
|
3
|
+
import ConfirmationState from "../../../src/ai/confirmation/ConfirmationState.svelte";
|
|
7
4
|
import ConfirmationActions from "../../../src/ai/confirmation/ConfirmationActions.svelte";
|
|
8
5
|
import ConfirmationAction from "../../../src/ai/confirmation/ConfirmationAction.svelte";
|
|
9
|
-
export { Confirmation, ConfirmationTitle,
|
|
6
|
+
export { Confirmation, ConfirmationTitle, ConfirmationState, ConfirmationActions, ConfirmationAction,
|
|
10
7
|
//
|
|
11
|
-
Confirmation as Root, ConfirmationTitle as Title,
|
|
8
|
+
Confirmation as Root, ConfirmationTitle as Title, ConfirmationState as State, ConfirmationActions as Actions, ConfirmationAction as Action };
|
package/dist/ai/index.js
CHANGED
|
@@ -17,11 +17,7 @@ export { PromptInput, PromptInputBody, PromptInputToolbar, PromptInputTextarea,
|
|
|
17
17
|
// =========================
|
|
18
18
|
// CONFIRMATION
|
|
19
19
|
// =========================
|
|
20
|
-
export { Confirmation, ConfirmationTitle,
|
|
21
|
-
// =========================
|
|
22
|
-
// THINKING DOTS
|
|
23
|
-
// =========================
|
|
24
|
-
export { ThinkingDots } from "./thinking-dots/index.js";
|
|
20
|
+
export { Confirmation, ConfirmationTitle, ConfirmationState, ConfirmationActions, ConfirmationAction } from "./confirmation/index.js";
|
|
25
21
|
// =========================
|
|
26
22
|
// VOICE WAVEFORM
|
|
27
23
|
// =========================
|
|
@@ -151,7 +151,10 @@ function patchEdgeOps(before, edge) {
|
|
|
151
151
|
function edgeOps(base, current) {
|
|
152
152
|
const baseEdges = new Map(base.edges.map((e) => [e.id, e]));
|
|
153
153
|
const currentIds = new Set(current.edges.map((e) => e.id));
|
|
154
|
-
const
|
|
154
|
+
const currentNodeIds = new Set(current.nodes.map((n) => n.id));
|
|
155
|
+
const removedNodeIds = new Set(base.nodes.filter((n) => !currentNodeIds.has(n.id)).map((n) => n.id));
|
|
156
|
+
// WHY: removeNode already takes the node's edges, and removeEdge throws on a missing id.
|
|
157
|
+
const removed = base.edges.filter((e) => !currentIds.has(e.id)).filter((e) => !removedNodeIds.has(String(e.source)) && !removedNodeIds.has(String(e.target))).map((e) => ({
|
|
155
158
|
kind: "removeEdge",
|
|
156
159
|
edgeId: e.id
|
|
157
160
|
}));
|
|
@@ -238,11 +241,32 @@ function parentsFirst(nodes) {
|
|
|
238
241
|
return sortParentsFirst(wrapped).map(({ node }) => node);
|
|
239
242
|
}
|
|
240
243
|
/**
|
|
244
|
+
* Drops removals the server already made, so a delete on both sides merges. Only
|
|
245
|
+
* removeNode and removeEdge: an edit to a node the server deleted must still throw.
|
|
246
|
+
*/
|
|
247
|
+
function withoutServerRemovals(serverDoc, ops) {
|
|
248
|
+
const serverNodeIds = new Set(serverDoc.nodes.map((n) => n.id));
|
|
249
|
+
const serverEdges = new Map(serverDoc.edges.map((e) => [e.id, e]));
|
|
250
|
+
const removedNodeIds = new Set();
|
|
251
|
+
return ops.filter((op) => {
|
|
252
|
+
if (op.kind === "removeNode") {
|
|
253
|
+
if (!serverNodeIds.has(op.nodeId)) return false;
|
|
254
|
+
removedNodeIds.add(op.nodeId);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (op.kind === "removeEdge") {
|
|
258
|
+
const edge = serverEdges.get(op.edgeId);
|
|
259
|
+
return edge !== undefined && !removedNodeIds.has(String(edge.source)) && !removedNodeIds.has(String(edge.target));
|
|
260
|
+
}
|
|
261
|
+
return true;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
241
265
|
* Replay local changes (lastSaved -> local) on the server document. Returns
|
|
242
266
|
* null when impossible, like a patched node deleted, so the save error shows.
|
|
243
267
|
*/
|
|
244
268
|
export function rebaseDiagram(serverDoc, lastSaved, local) {
|
|
245
|
-
const ops = diffToOps(lastSaved, local);
|
|
269
|
+
const ops = withoutServerRemovals(serverDoc, diffToOps(lastSaved, local));
|
|
246
270
|
const res = tryCatch(() => applyOps({
|
|
247
271
|
nodes: serverDoc.nodes,
|
|
248
272
|
edges: serverDoc.edges
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
export { default as DataPanel } from "../../../src/
|
|
2
|
-
export { default as DataSortHead } from "../../../src/charts/dashboard/data-sort-head.svelte";
|
|
1
|
+
export { default as DataPanel } from "../../../src/lib/panel.svelte";
|
|
3
2
|
export { default as DataTable } from "../../../src/charts/dashboard/data-table.svelte";
|
|
4
3
|
export { default as RatingChip } from "../../../src/charts/dashboard/rating-chip.svelte";
|
|
5
|
-
export { default as DashboardEmpty } from "../../../src/charts/dashboard/dashboard-empty.svelte";
|
|
6
4
|
export { default as DashboardSection } from "../../../src/charts/dashboard/dashboard-section.svelte";
|
|
7
5
|
export { default as PanelGrid } from "../../../src/charts/dashboard/panel-grid.svelte";
|
|
8
6
|
export { default as PeriodFilter } from "../../../src/charts/dashboard/period-filter.svelte";
|
|
@@ -13,6 +13,20 @@ export function getPayloadConfigFromPayload(config, item, key) {
|
|
|
13
13
|
}
|
|
14
14
|
return configLabelKey in config ? config[configLabelKey] : config[key];
|
|
15
15
|
}
|
|
16
|
+
const compact = new Intl.NumberFormat(undefined, {
|
|
17
|
+
notation: "compact",
|
|
18
|
+
maximumFractionDigits: 1
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* An axis tick formatter. "1,000,000" overflows the 48px gutter and the gap between ticks, so a
|
|
22
|
+
* label over 6 characters falls back to "1M"; the tooltip keeps the full value.
|
|
23
|
+
*/
|
|
24
|
+
export function valueTick(format) {
|
|
25
|
+
return (v) => {
|
|
26
|
+
const full = format(v);
|
|
27
|
+
return full.length > 6 && Number.isFinite(v) ? compact.format(v) : full;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
16
30
|
const chartContextKey = Symbol("chart-context");
|
|
17
31
|
export function setChartContext(value) {
|
|
18
32
|
return setContext(chartContextKey, value);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export { default as DetailEmpty } from "../../../src/components/list-page/detail-empty.svelte";
|
|
2
1
|
export { default as DetailHeader } from "../../../src/components/list-page/detail-header.svelte";
|
|
3
|
-
export { default as DetailPanel } from "../../../src/
|
|
2
|
+
export { default as DetailPanel } from "../../../src/lib/panel.svelte";
|
|
4
3
|
export { default as DetailRow } from "../../../src/components/list-page/detail-row.svelte";
|
|
5
4
|
export { default as DetailStack } from "../../../src/components/list-page/detail-stack.svelte";
|
|
6
5
|
export { default as EmptyState } from "../../../src/components/list-page/empty-state.svelte";
|
|
@@ -9,7 +8,6 @@ export { default as PrevNext } from "../../../src/components/list-page/prev-next
|
|
|
9
8
|
export { stepTo } from "./step.js";
|
|
10
9
|
export { default as NoticeBar } from "../../../src/components/list-page/notice-bar.svelte";
|
|
11
10
|
export { default as RailBody } from "../../../src/components/list-page/rail-body.svelte";
|
|
12
|
-
export { default as RailEmpty } from "../../../src/components/list-page/rail-empty.svelte";
|
|
13
11
|
export { default as RailHead } from "../../../src/components/list-page/rail-head.svelte";
|
|
14
12
|
export { default as RailRow } from "../../../src/components/list-page/rail-row.svelte";
|
|
15
13
|
export { default as SectionLabel } from "../../../src/components/section-header/section-label.svelte";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { default as CountChip } from "../../../src/components/
|
|
1
|
+
export { default as CountChip } from "../../../src/components/chip/count-chip.svelte";
|
|
2
2
|
export { default as GroupHead } from "../../../src/components/work-parts/group-head.svelte";
|
|
3
3
|
export { default as PropertyRow } from "../../../src/components/work-parts/property-row.svelte";
|
|
4
4
|
export { default as WorkCard } from "../../../src/components/work-parts/work-card.svelte";
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Extension } from "@tiptap/core";
|
|
6
6
|
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
7
7
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
8
|
+
import { copyText } from "../components/copy/clipboard.js";
|
|
8
9
|
import { anchorIds } from "./anchors.js";
|
|
9
10
|
const LINK_ICON = `<svg width="12" height="12" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M240 88.23a54.43 54.43 0 0 1-16 37L189.25 160a54.27 54.27 0 0 1-38.63 16h-.05A54.63 54.63 0 0 1 96 119.84a8 8 0 0 1 16 .45A38.62 38.62 0 0 0 150.58 160a38.4 38.4 0 0 0 27.31-11.31l34.75-34.75a38.63 38.63 0 0 0-54.63-54.63l-11 11A8 8 0 0 1 135.7 59l11-11a54.65 54.65 0 0 1 77.3 0A54.86 54.86 0 0 1 240 88.23ZM109 185.66l-11 11A38.41 38.41 0 0 1 70.6 208a38.63 38.63 0 0 1-27.29-65.94L78 107.31a38.63 38.63 0 0 1 54.63 0 8 8 0 0 0 11.31-11.31 54.65 54.65 0 0 0-77.3 0L32 130.75A54.62 54.62 0 0 0 70.56 224a54.28 54.28 0 0 0 38.64-16l11-11a8 8 0 0 0-11.2-11.34Z"/></svg>`;
|
|
10
11
|
const anchorsKey = new PluginKey("headingAnchors");
|
|
@@ -48,7 +49,7 @@ export const HeadingAnchors = Extension.create({
|
|
|
48
49
|
button.addEventListener("click", () => {
|
|
49
50
|
const { origin, pathname, search } = window.location;
|
|
50
51
|
const url = `${origin}${pathname}${search}#${id}`;
|
|
51
|
-
void
|
|
52
|
+
void copyText(url);
|
|
52
53
|
button.dataset.copied = "true";
|
|
53
54
|
setTimeout(() => delete button.dataset.copied, 1400);
|
|
54
55
|
options.oncopy?.(url, id);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const SAFE_SCHEME = /^(?:https?:|mailto:|tel:)/i;
|
|
2
|
+
const ANY_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
3
|
+
// WHY no SVG: an SVG can carry script, and pasted images are stored as these data: URLs.
|
|
4
|
+
const SAFE_IMAGE_DATA = /^data:image\/(?:png|jpe?g|gif|webp|avif);base64,/i;
|
|
5
|
+
// WHY: browsers drop tabs, newlines and control bytes from a URL, so "java\tscript:" runs as javascript:.
|
|
6
|
+
function clean(raw) {
|
|
7
|
+
return [...raw].filter((c) => c.charCodeAt(0) > 31 && c.charCodeAt(0) !== 127).join("").trim();
|
|
8
|
+
}
|
|
9
|
+
// The URL when it's safe to put in an href, else null.
|
|
10
|
+
export function safeUrl(raw) {
|
|
11
|
+
if (!raw) return null;
|
|
12
|
+
const href = clean(raw);
|
|
13
|
+
// Relative URLs and same-page anchors carry no scheme, so they are safe.
|
|
14
|
+
if (href.startsWith("#") || href.startsWith("/") || href.startsWith(".")) return href;
|
|
15
|
+
if (SAFE_SCHEME.test(href)) return href;
|
|
16
|
+
if (!ANY_SCHEME.test(href)) return href;
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
// The URL when it's safe to put in an img src, else null.
|
|
20
|
+
export function safeImageUrl(raw) {
|
|
21
|
+
const url = safeUrl(raw);
|
|
22
|
+
if (url !== null) return url;
|
|
23
|
+
if (!raw) return null;
|
|
24
|
+
const src = clean(raw);
|
|
25
|
+
return SAFE_IMAGE_DATA.test(src) ? src : null;
|
|
26
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { safeImageUrl, safeUrl } from "./safe-url.js";
|
|
1
2
|
// WHY our own slugify: the website's uses NFKD, which strips accents from French
|
|
2
3
|
// headings. The vibe-kit utils one doesn't, so accented anchors would differ.
|
|
3
4
|
function slugify(input, maxLength = 60) {
|
|
@@ -99,7 +100,7 @@ function numString(attrs, key) {
|
|
|
99
100
|
return value === undefined ? undefined : String(value);
|
|
100
101
|
}
|
|
101
102
|
function renderImage(node) {
|
|
102
|
-
const src = strAttr(node.attrs, "src");
|
|
103
|
+
const src = safeImageUrl(strAttr(node.attrs, "src"));
|
|
103
104
|
if (!src) return "";
|
|
104
105
|
const attrs = renderAttributes({
|
|
105
106
|
alt: strAttr(node.attrs, "alt") ?? "",
|
|
@@ -237,10 +238,15 @@ const MARK_TAGS = {
|
|
|
237
238
|
};
|
|
238
239
|
function wrapMark(content, mark) {
|
|
239
240
|
if (mark.type === "link") {
|
|
241
|
+
// WHY: stored docs come from teammates, clients and CLIs that skip the editor's link filter.
|
|
242
|
+
const href = safeUrl(strAttr(mark.attrs, "href"));
|
|
243
|
+
if (!href) return content;
|
|
244
|
+
const target = strAttr(mark.attrs, "target");
|
|
245
|
+
const rel = strAttr(mark.attrs, "rel") || (target ? "noopener noreferrer" : undefined);
|
|
240
246
|
const attrs = renderAttributes({
|
|
241
|
-
href
|
|
242
|
-
target
|
|
243
|
-
rel
|
|
247
|
+
href,
|
|
248
|
+
target,
|
|
249
|
+
rel
|
|
244
250
|
});
|
|
245
251
|
return `<a${attrs}>${content}</a>`;
|
|
246
252
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// One character of context, the slash, and a query of up to 20.
|
|
2
|
+
export const SLASH_WINDOW = 22;
|
|
3
|
+
/**
|
|
4
|
+
* The slash command typed just before the caret, or null when there isn't one. The slash has to
|
|
5
|
+
* start the text or follow whitespace, so `TCP/IP` and `and/or` stay plain text.
|
|
6
|
+
*/
|
|
7
|
+
export function slashQueryAt(textBefore) {
|
|
8
|
+
const match = textBefore.match(/(?:^|\s)\/([a-zA-Z0-9 ]{0,20})$/);
|
|
9
|
+
return match ? match[1] : null;
|
|
10
|
+
}
|
|
@@ -15,14 +15,21 @@ export function usePathCalculator() {
|
|
|
15
15
|
const containerRect = containerRef.getBoundingClientRect();
|
|
16
16
|
const rectA = fromRef.getBoundingClientRect();
|
|
17
17
|
const rectB = toRef.getBoundingClientRect();
|
|
18
|
+
// A scaled ancestor shrinks every rect, but the SVG draws in the container's own pixels.
|
|
19
|
+
// Divide the scale out, or the beams stop short of their nodes.
|
|
20
|
+
const scaleX = containerRef.offsetWidth ? containerRect.width / containerRef.offsetWidth : 1;
|
|
21
|
+
const scaleY = containerRef.offsetHeight ? containerRect.height / containerRef.offsetHeight : 1;
|
|
22
|
+
const local = (rect) => new DOMRect((rect.left - containerRect.left) / scaleX, (rect.top - containerRect.top) / scaleY, rect.width / scaleX, rect.height / scaleY);
|
|
23
|
+
const a = local(rectA);
|
|
24
|
+
const b = local(rectB);
|
|
18
25
|
svgDimensions = {
|
|
19
|
-
width: containerRect.width,
|
|
20
|
-
height: containerRect.height
|
|
26
|
+
width: containerRect.width / scaleX,
|
|
27
|
+
height: containerRect.height / scaleY
|
|
21
28
|
};
|
|
22
|
-
const startX = anchorX(
|
|
23
|
-
const startY =
|
|
24
|
-
const endX = anchorX(
|
|
25
|
-
const endY =
|
|
29
|
+
const startX = anchorX(a, 0, fromAnchor, startXOffset);
|
|
30
|
+
const startY = a.top + a.height / 2 + startYOffset;
|
|
31
|
+
const endX = anchorX(b, 0, toAnchor, endXOffset);
|
|
32
|
+
const endY = b.top + b.height / 2 + endYOffset;
|
|
26
33
|
// A cubic with horizontal handles, not a quadratic through a raised midpoint.
|
|
27
34
|
// The quadratic put its single control point at `midY` minus `curvature`, which for a fan into one node lands past the far end:
|
|
28
35
|
// the top beam dove below the hub and crossed the middle beam before arriving, and three beams bundled into a lens for the last stretch.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** "Oct 23" in the caller's locale. */
|
|
2
|
+
export function shortDate(at, { locale, timeZone, year } = {}) {
|
|
3
|
+
return new Intl.DateTimeFormat(locale, {
|
|
4
|
+
month: "short",
|
|
5
|
+
day: "numeric",
|
|
6
|
+
year: year ? "numeric" : undefined,
|
|
7
|
+
timeZone
|
|
8
|
+
}).format(new Date(at));
|
|
9
|
+
}
|
|
10
|
+
const UNITS = [
|
|
11
|
+
[31536e3, "year"],
|
|
12
|
+
[2592e3, "month"],
|
|
13
|
+
[604800, "week"],
|
|
14
|
+
[86400, "day"],
|
|
15
|
+
[3600, "hour"],
|
|
16
|
+
[60, "minute"]
|
|
17
|
+
];
|
|
18
|
+
/** "3 days ago", "in 2 weeks", "now": the largest whole unit between `at` and `now`. */
|
|
19
|
+
export function relativeTime(at, now = Date.now(), { locale, style = "long" } = {}) {
|
|
20
|
+
const seconds = Math.round((new Date(at).getTime() - new Date(now).getTime()) / 1e3);
|
|
21
|
+
const age = Math.abs(seconds);
|
|
22
|
+
const rtf = new Intl.RelativeTimeFormat(locale, {
|
|
23
|
+
numeric: "auto",
|
|
24
|
+
style
|
|
25
|
+
});
|
|
26
|
+
for (const [size, unit] of UNITS) {
|
|
27
|
+
if (age >= size) return rtf.format(Math.round(seconds / size), unit);
|
|
28
|
+
}
|
|
29
|
+
return rtf.format(0, "second");
|
|
30
|
+
}
|
|
31
|
+
/** The calendar day of a moment as `YYYY-MM-DD`, in the given zone or the viewer's own. Today by default. */
|
|
32
|
+
export function localDay(at = new Date(), timeZone) {
|
|
33
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
34
|
+
year: "numeric",
|
|
35
|
+
month: "2-digit",
|
|
36
|
+
day: "2-digit",
|
|
37
|
+
timeZone
|
|
38
|
+
}).formatToParts(new Date(at));
|
|
39
|
+
const part = (type) => parts.find((p) => p.type === type)?.value ?? "";
|
|
40
|
+
return `${part("year")}-${part("month")}-${part("day")}`;
|
|
41
|
+
}
|
package/dist/lib/panel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** The one panel shell: a titled box holding a table, a chart or a stack. */
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// Tokens beside `panel.svelte`, because skeletons and hand-built cards draw this
|
|
3
|
+
// same edge without the panel's header.
|
|
4
4
|
// Dashboards used `Card`, so /ads had a ring and a shadow where /social had a
|
|
5
5
|
// hairline. Flat, one hairline, `rounded-xl`.
|
|
6
6
|
export const panelShellClass = "overflow-hidden rounded-xl border border-border bg-card";
|
package/dist/lib/tone.js
CHANGED
|
@@ -10,6 +10,19 @@ export const TONE_CLASS = {
|
|
|
10
10
|
warning: "border-warning/30 bg-warning/10 text-warning-800 dark:text-warning-400",
|
|
11
11
|
destructive: "border-destructive/30 bg-destructive/10 text-destructive-ink"
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* The scale as a quiet tint with no border: the icon and the text carry the tone, the fill only
|
|
15
|
+
* groups them. Neutral reads off the foreground, not `bg-muted`, because muted sits 0.005 L from
|
|
16
|
+
* the canvas and vanishes there (tokens board, "what fights the new scale").
|
|
17
|
+
*/
|
|
18
|
+
export const TONE_TINT = {
|
|
19
|
+
neutral: "bg-foreground/6 text-muted-foreground",
|
|
20
|
+
accent: "bg-[color-mix(in_oklab,var(--tone-accent,var(--color-primary))_12%,transparent)] text-[color:var(--tone-accent,var(--color-primary))]",
|
|
21
|
+
info: "bg-info/12 text-info-800 dark:text-info-400",
|
|
22
|
+
success: "bg-success/12 text-success-800 dark:text-success-400",
|
|
23
|
+
warning: "bg-warning/14 text-warning-800 dark:text-warning-400",
|
|
24
|
+
destructive: "bg-destructive/10 text-destructive-ink"
|
|
25
|
+
};
|
|
13
26
|
/** The same scale as text, for a verdict inside a line of muted facts. */
|
|
14
27
|
export const TONE_TEXT = {
|
|
15
28
|
neutral: "text-muted-foreground",
|
package/dist/record/format.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Display formatting through Intl, so a locale string is all a host passes.
|
|
2
2
|
// A date-only value ("2026-10-26") is a calendar day, read in UTC so no zone shifts it.
|
|
3
|
+
import { relativeTime } from "../lib/dates.js";
|
|
3
4
|
const DAY_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
4
5
|
function parse(iso) {
|
|
5
6
|
return DAY_ONLY.test(iso) ? {
|
|
@@ -55,24 +56,7 @@ export function formatTime(iso, locale = "en-GB", timeZone) {
|
|
|
55
56
|
}
|
|
56
57
|
/** "3 days ago", "in 2 weeks", "now". */
|
|
57
58
|
export function formatAge(iso, now = Date.now(), locale = "en-GB") {
|
|
58
|
-
|
|
59
|
-
const abs = Math.abs(seconds);
|
|
60
|
-
const rtf = new Intl.RelativeTimeFormat(locale, {
|
|
61
|
-
numeric: "auto",
|
|
62
|
-
style: "long"
|
|
63
|
-
});
|
|
64
|
-
const units = [
|
|
65
|
-
[31536e3, "year"],
|
|
66
|
-
[2592e3, "month"],
|
|
67
|
-
[604800, "week"],
|
|
68
|
-
[86400, "day"],
|
|
69
|
-
[3600, "hour"],
|
|
70
|
-
[60, "minute"]
|
|
71
|
-
];
|
|
72
|
-
for (const [size, unit] of units) {
|
|
73
|
-
if (abs >= size) return rtf.format(Math.round(seconds / size), unit);
|
|
74
|
-
}
|
|
75
|
-
return rtf.format(0, "second");
|
|
59
|
+
return relativeTime(iso, now, { locale });
|
|
76
60
|
}
|
|
77
61
|
/** "€12,000". `compact` gives "€12K". */
|
|
78
62
|
export function formatMoney(amount, currency = "EUR", locale = "en-GB", compact = false) {
|
package/dist/record/logic.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
+
// The record's pure logic: grouping, merging, picking. No DOM, no clock of its own.
|
|
2
|
+
import { localDay as dayOf } from "../lib/dates.js";
|
|
1
3
|
// =========================
|
|
2
4
|
// EVENTS
|
|
3
5
|
// =========================
|
|
4
6
|
/** The calendar day of a timestamp as `YYYY-MM-DD`, in the given zone or the viewer's own. */
|
|
5
7
|
export function localDay(iso, timeZone) {
|
|
6
|
-
return
|
|
7
|
-
year: "numeric",
|
|
8
|
-
month: "2-digit",
|
|
9
|
-
day: "2-digit",
|
|
10
|
-
timeZone
|
|
11
|
-
}).format(new Date(iso));
|
|
8
|
+
return dayOf(iso, timeZone);
|
|
12
9
|
}
|
|
13
10
|
/** Newest day first, newest event first inside a day. */
|
|
14
11
|
export function groupEventsByDay(events, timeZone) {
|
package/dist/status/index.js
CHANGED
|
@@ -3,4 +3,4 @@ export { default as StatusDot } from "../../src/status/status-dot.svelte";
|
|
|
3
3
|
export { default as StatusIcon } from "../../src/status/status-icon.svelte";
|
|
4
4
|
export { defineStatuses } from "./define-statuses.js";
|
|
5
5
|
export { default as StatusPill } from "../../src/status/status-pill.svelte";
|
|
6
|
-
export { PILL_TINT } from "
|
|
6
|
+
export { TONE_TINT as PILL_TINT } from "../lib/tone.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as TriagePlan } from "../../src/triage/triage-plan.svelte";
|
|
2
|
+
export { default as TriageTask } from "../../src/triage/triage-task.svelte";
|
|
3
|
+
export { default as TriageQueue } from "../../src/triage/triage-queue.svelte";
|
|
4
|
+
export { DEFAULT_TRIAGE_PLAN_LABELS, DEFAULT_TRIAGE_TASK_LABELS } from "./types.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const DEFAULT_TRIAGE_PLAN_LABELS = {
|
|
2
|
+
approve: "Approve",
|
|
3
|
+
pick: "Pick another task",
|
|
4
|
+
dismiss: "Dismiss",
|
|
5
|
+
taskLabel: "Task",
|
|
6
|
+
useTask: "Use this task",
|
|
7
|
+
cancel: "Cancel"
|
|
8
|
+
};
|
|
9
|
+
export const DEFAULT_TRIAGE_TASK_LABELS = {
|
|
10
|
+
undo: "Undo",
|
|
11
|
+
openTask: "Open task"
|
|
12
|
+
};
|
package/dist/work/dates.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Dates are plain "YYYY-MM-DD" strings, counted as whole UTC days. A timeline
|
|
2
2
|
// only needs days, and day numbers dodge every timezone and DST edge.
|
|
3
|
+
import { localDay } from "../lib/dates.js";
|
|
3
4
|
const DAY_MS = 864e5;
|
|
4
5
|
/** Days since 1970-01-01 for an ISO date. Anything after the date part is ignored. */
|
|
5
6
|
export function dayNumber(iso) {
|
|
@@ -16,9 +17,7 @@ export function addDays(iso, days) {
|
|
|
16
17
|
}
|
|
17
18
|
/** Today in the viewer's own timezone, as an ISO date. */
|
|
18
19
|
export function todayIso() {
|
|
19
|
-
|
|
20
|
-
const local = new Date(now.getTime() - now.getTimezoneOffset() * 6e4);
|
|
21
|
-
return local.toISOString().slice(0, 10);
|
|
20
|
+
return localDay();
|
|
22
21
|
}
|
|
23
22
|
/** 0 is Sunday, as in `Date.getUTCDay`. */
|
|
24
23
|
export function weekday(day) {
|
|
@@ -8,13 +8,5 @@ export const DEFAULT_TASK_DETAIL_LABELS = {
|
|
|
8
8
|
save: "Save",
|
|
9
9
|
saving: "Saving",
|
|
10
10
|
cancel: "Cancel",
|
|
11
|
-
comments: "Activity",
|
|
12
|
-
commentPlaceholder: "Leave a comment",
|
|
13
|
-
commentSend: "Comment",
|
|
14
|
-
commentEdit: "Edit comment",
|
|
15
|
-
commentDelete: "Delete comment",
|
|
16
|
-
commentEdited: "edited",
|
|
17
|
-
commentsEmpty: "No comments yet",
|
|
18
|
-
ai: "AI",
|
|
19
11
|
properties: "Properties"
|
|
20
12
|
};
|
package/dist/work/docs/edited.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** The "Edited Oct 23" chip: how a stamp reads, and what to show when it can't be read. */
|
|
2
|
+
import { shortDate as monthDay } from "../../lib/dates.js";
|
|
1
3
|
export function parseStamp(value) {
|
|
2
4
|
if (value === null || value === undefined || value === "") return null;
|
|
3
5
|
const date = value instanceof Date ? value : new Date(value);
|
|
@@ -14,13 +16,12 @@ export function shortDate(value, options = {}) {
|
|
|
14
16
|
const date = parseStamp(value);
|
|
15
17
|
if (!date) return "";
|
|
16
18
|
const { locale = "en-US", timeZone, now = new Date() } = options;
|
|
17
|
-
const
|
|
18
|
-
return
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
year
|
|
22
|
-
|
|
23
|
-
}).format(date);
|
|
19
|
+
const year = yearOf(date, options) !== yearOf(now, options);
|
|
20
|
+
return monthDay(date, {
|
|
21
|
+
locale,
|
|
22
|
+
timeZone,
|
|
23
|
+
year
|
|
24
|
+
});
|
|
24
25
|
}
|
|
25
26
|
/** "Oct 23, 14:44" for the last-edit row. Empty when the date can't be read. */
|
|
26
27
|
export function editStamp(value, options = {}) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { relativeTime, shortDate } from "../../lib/dates.js";
|
|
1
2
|
/** The draft to open on: the agent's pick, else the first. */
|
|
2
3
|
export function startDraft(drafts, recommended) {
|
|
3
4
|
return drafts.find((draft) => draft.templateId === recommended) ?? drafts[0];
|
|
@@ -52,12 +53,6 @@ export function freshReasoning(item, facts) {
|
|
|
52
53
|
return said.some((line) => line.includes(key) || key.includes(line)) ? "" : reasoning;
|
|
53
54
|
}
|
|
54
55
|
const ISO = /^\d{4}-\d{2}-\d{2}T/;
|
|
55
|
-
const UNITS = [
|
|
56
|
-
[604800, "week"],
|
|
57
|
-
[86400, "day"],
|
|
58
|
-
[3600, "hour"],
|
|
59
|
-
[60, "minute"]
|
|
60
|
-
];
|
|
61
56
|
/**
|
|
62
57
|
* A time as a person says it: "3 hr. ago", "Oct 12". A host-formatted string stays as written,
|
|
63
58
|
* and a raw ISO string never reaches the screen.
|
|
@@ -66,22 +61,12 @@ export function whenOf(at, now = Date.now(), locale = "en-GB") {
|
|
|
66
61
|
if (!ISO.test(at)) return at;
|
|
67
62
|
const time = Date.parse(at);
|
|
68
63
|
if (Number.isNaN(time)) return "";
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
month: "short",
|
|
74
|
-
day: "numeric"
|
|
75
|
-
}).format(time);
|
|
76
|
-
}
|
|
77
|
-
const rtf = new Intl.RelativeTimeFormat(locale, {
|
|
78
|
-
numeric: "auto",
|
|
64
|
+
const age = Math.abs(Math.round((time - now) / 1e3));
|
|
65
|
+
if (age >= 4 * 604800) return shortDate(time, { locale });
|
|
66
|
+
return relativeTime(time, now, {
|
|
67
|
+
locale,
|
|
79
68
|
style: "short"
|
|
80
69
|
});
|
|
81
|
-
for (const [size, unit] of UNITS) {
|
|
82
|
-
if (age >= size) return rtf.format(Math.round(seconds / size), unit);
|
|
83
|
-
}
|
|
84
|
-
return rtf.format(0, "second");
|
|
85
70
|
}
|
|
86
71
|
/** True when a key press belongs to a text field and so isn't a shortcut. */
|
|
87
72
|
export function isTyping(target) {
|
package/dist/work/index.js
CHANGED
|
@@ -8,8 +8,6 @@ export { default as TaskList } from "../../src/work/task-list.svelte";
|
|
|
8
8
|
export { default as TaskGantt } from "../../src/work/task-gantt.svelte";
|
|
9
9
|
export { addDays, dayNumber, isoFromDay, todayIso } from "./dates.js";
|
|
10
10
|
export { default as TaskDetail } from "../../src/work/detail/task-detail.svelte";
|
|
11
|
-
export { default as TaskThread } from "../../src/work/detail/task-thread.svelte";
|
|
12
|
-
export { default as TaskProperty } from "../../src/work/detail/task-property.svelte";
|
|
13
11
|
export { default as TaskChip } from "../../src/work/detail/task-chip.svelte";
|
|
14
12
|
export { DEFAULT_TASK_DETAIL_LABELS } from "./detail/detail-types.js";
|
|
15
13
|
export { default as TimeTracker } from "../../src/work/time/time-tracker.svelte";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { localDay as dayOf } from "../../lib/dates.js";
|
|
1
2
|
/** "90", "45m", "1h", "1.5h", "1h30", "1h 30m" and "1:30" to whole minutes. Null when it isn't a duration. */
|
|
2
3
|
export function parseMinutes(text) {
|
|
3
4
|
const value = text.trim().toLowerCase();
|
|
@@ -30,9 +31,7 @@ export function formatClock(ms) {
|
|
|
30
31
|
}
|
|
31
32
|
/** The reader's own calendar day as `YYYY-MM-DD`. */
|
|
32
33
|
export function localDay(date = new Date()) {
|
|
33
|
-
|
|
34
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
35
|
-
return `${date.getFullYear()}-${month}-${day}`;
|
|
34
|
+
return dayOf(date);
|
|
36
35
|
}
|
|
37
36
|
/** "Today", "Yesterday", then "Mon, Oct 12", in the given locale. */
|
|
38
37
|
export function dayLabel(day, locale) {
|