@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
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
import { Card } from "../../shadcn/card/index.js";
|
|
14
14
|
import * as Avatar from "../../shadcn/avatar/index.js";
|
|
15
15
|
import TaskDetail from "./task-detail.svelte";
|
|
16
|
-
import
|
|
16
|
+
import PropertyRow from "../../components/work-parts/property-row.svelte";
|
|
17
17
|
import TaskChip from "./task-chip.svelte";
|
|
18
18
|
import TimeTracker from "../time/time-tracker.svelte";
|
|
19
19
|
import StatusDot from "../../status/status-dot.svelte";
|
|
20
20
|
import { defineStatuses } from "../../status/define-statuses.js";
|
|
21
|
-
import type {
|
|
21
|
+
import type { ThreadComment, ThreadPost } from "../../thread/types.js";
|
|
22
22
|
import type { TimeLogInput, TimeStopInfo } from "../time/time-types.js";
|
|
23
23
|
|
|
24
24
|
type Scenario =
|
|
@@ -53,32 +53,33 @@
|
|
|
53
53
|
const hour = 3_600_000;
|
|
54
54
|
const stamp = (agoHours: number) => new Date(now - agoHours * hour).toISOString();
|
|
55
55
|
|
|
56
|
-
const
|
|
56
|
+
const person = (name: string) => ({ name, kind: "user" as const });
|
|
57
|
+
const planner = { name: "Planner", kind: "agent" as const };
|
|
58
|
+
|
|
59
|
+
const seed: ThreadComment[] = [
|
|
57
60
|
{
|
|
58
61
|
id: "c1",
|
|
59
|
-
author: "Ada Lovelace",
|
|
62
|
+
author: person("Ada Lovelace"),
|
|
60
63
|
body: "Calls with Yoan: the format that sold the template was simple. A board on why, then the system, then the demo.",
|
|
61
64
|
createdAt: stamp(30),
|
|
62
65
|
},
|
|
63
66
|
{
|
|
64
67
|
id: "c2",
|
|
65
|
-
author:
|
|
66
|
-
agent: true,
|
|
68
|
+
author: planner,
|
|
67
69
|
body: "Reused that shape for the outline. Three sections, the demo last, one call to action at the end.",
|
|
68
70
|
createdAt: stamp(26),
|
|
69
71
|
},
|
|
70
72
|
{
|
|
71
73
|
id: "c3",
|
|
72
|
-
author: "Bo Chen",
|
|
74
|
+
author: person("Bo Chen"),
|
|
73
75
|
body: "Looks right. I'll record Thursday.",
|
|
74
76
|
createdAt: stamp(5),
|
|
75
77
|
edited: true,
|
|
76
78
|
},
|
|
77
79
|
];
|
|
78
|
-
const crowd:
|
|
80
|
+
const crowd: ThreadComment[] = Array.from({ length: 24 }, (_, i) => ({
|
|
79
81
|
id: `b${i}`,
|
|
80
|
-
author: i % 4 === 1 ?
|
|
81
|
-
agent: i % 4 === 1,
|
|
82
|
+
author: i % 4 === 1 ? planner : person(people[i % people.length] ?? "Ada Lovelace"),
|
|
82
83
|
body:
|
|
83
84
|
i % 5 === 0
|
|
84
85
|
? "Pushed the second cut. The intro runs 40 seconds, so I trimmed the first example and kept the one with numbers."
|
|
@@ -95,7 +96,7 @@
|
|
|
95
96
|
let narrow = $state(false);
|
|
96
97
|
let title = $state("Record the video and hand it off");
|
|
97
98
|
let description = $state<string | null>(brief);
|
|
98
|
-
let thread = $state<
|
|
99
|
+
let thread = $state<ThreadComment[]>([...seed]);
|
|
99
100
|
let done = $state(false);
|
|
100
101
|
let timer = $state<{ startedAt: number } | null>(null);
|
|
101
102
|
let entries = $state([
|
|
@@ -140,13 +141,14 @@
|
|
|
140
141
|
timer = null;
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
function
|
|
144
|
+
function onPostComment({ body, parentId }: ThreadPost) {
|
|
144
145
|
thread = [
|
|
145
146
|
...thread,
|
|
146
147
|
{
|
|
147
148
|
id: `n${thread.length}`,
|
|
148
|
-
author: "Ada Lovelace",
|
|
149
|
+
author: person("Ada Lovelace"),
|
|
149
150
|
body,
|
|
151
|
+
parentId,
|
|
150
152
|
createdAt: new Date().toISOString(),
|
|
151
153
|
},
|
|
152
154
|
];
|
|
@@ -191,27 +193,28 @@
|
|
|
191
193
|
}}
|
|
192
194
|
comments={thread}
|
|
193
195
|
loadingComments={scenario === "loading"}
|
|
194
|
-
{
|
|
196
|
+
{onPostComment}
|
|
195
197
|
onDeleteComment={(comment) => {
|
|
196
198
|
thread = thread.filter((row) => row.id !== comment.id);
|
|
197
199
|
}}
|
|
198
|
-
canEditComment={(comment) =>
|
|
200
|
+
canEditComment={(comment) =>
|
|
201
|
+
comment.author.kind === "user" && comment.author.name === "Ada Lovelace"}
|
|
199
202
|
>
|
|
200
203
|
{#snippet meta()}
|
|
201
204
|
<span>From a call</span>
|
|
202
205
|
<span class="tabular-nums">Created Sep 9, 2026</span>
|
|
203
206
|
{/snippet}
|
|
204
207
|
{#snippet properties()}
|
|
205
|
-
<
|
|
208
|
+
<PropertyRow label="Status" icon={CircleHalf} layout="responsive">
|
|
206
209
|
<TaskChip aria-label="Status: {statusLabel}"
|
|
207
210
|
><StatusDot {statuses} {status} size={14} animate={false} />{statusLabel}</TaskChip
|
|
208
211
|
>
|
|
209
|
-
</
|
|
210
|
-
<
|
|
212
|
+
</PropertyRow>
|
|
213
|
+
<PropertyRow label="Priority" icon={Flag} layout="responsive">
|
|
211
214
|
<TaskChip><Flag weight="fill" class="text-destructive" aria-hidden="true" />High</TaskChip
|
|
212
215
|
>
|
|
213
|
-
</
|
|
214
|
-
<
|
|
216
|
+
</PropertyRow>
|
|
217
|
+
<PropertyRow label="Assignee" icon={UserCircle} layout="responsive">
|
|
215
218
|
{#if assignee}
|
|
216
219
|
<TaskChip>
|
|
217
220
|
<Avatar.Root class="size-4" aria-hidden="true">
|
|
@@ -222,14 +225,14 @@
|
|
|
222
225
|
{:else}
|
|
223
226
|
<TaskChip empty><CircleDashed aria-hidden="true" />Assign</TaskChip>
|
|
224
227
|
{/if}
|
|
225
|
-
</
|
|
226
|
-
<
|
|
228
|
+
</PropertyRow>
|
|
229
|
+
<PropertyRow label="Due date" icon={CalendarBlank} layout="responsive">
|
|
227
230
|
<TaskChip><CalendarBlank aria-hidden="true" />Oct 9, 2026</TaskChip>
|
|
228
|
-
</
|
|
229
|
-
<
|
|
231
|
+
</PropertyRow>
|
|
232
|
+
<PropertyRow label="Project" icon={Folder} layout="responsive">
|
|
230
233
|
<TaskChip><Folder aria-hidden="true" />Agency OS</TaskChip>
|
|
231
|
-
</
|
|
232
|
-
<
|
|
234
|
+
</PropertyRow>
|
|
235
|
+
<PropertyRow label="Time" icon={Timer} layout="responsive">
|
|
233
236
|
<TimeTracker
|
|
234
237
|
{entries}
|
|
235
238
|
running={timer}
|
|
@@ -248,7 +251,7 @@
|
|
|
248
251
|
entries = entries.filter((row) => row.id !== entry.id);
|
|
249
252
|
}}
|
|
250
253
|
/>
|
|
251
|
-
</
|
|
254
|
+
</PropertyRow>
|
|
252
255
|
{/snippet}
|
|
253
256
|
</TaskDetail>
|
|
254
257
|
</Card>
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export type TaskComment = {
|
|
3
|
-
id: string;
|
|
4
|
-
author: string;
|
|
5
|
-
body: string;
|
|
6
|
-
/** ISO timestamp. */
|
|
7
|
-
createdAt: string;
|
|
8
|
-
/** An agent wrote it: the thread draws the agent avatar and an "AI" label. */
|
|
9
|
-
agent?: boolean;
|
|
10
|
-
/** Set when the body changed after posting. */
|
|
11
|
-
edited?: boolean;
|
|
12
|
-
};
|
|
13
|
-
|
|
1
|
+
/** The task page's own words. The comment thread's come through `threadLabels`. */
|
|
14
2
|
export type TaskDetailLabels = {
|
|
15
3
|
markComplete: string;
|
|
16
4
|
markIncomplete: string;
|
|
@@ -21,14 +9,6 @@ export type TaskDetailLabels = {
|
|
|
21
9
|
save: string;
|
|
22
10
|
saving: string;
|
|
23
11
|
cancel: string;
|
|
24
|
-
comments: string;
|
|
25
|
-
commentPlaceholder: string;
|
|
26
|
-
commentSend: string;
|
|
27
|
-
commentEdit: string;
|
|
28
|
-
commentDelete: string;
|
|
29
|
-
commentEdited: string;
|
|
30
|
-
commentsEmpty: string;
|
|
31
|
-
ai: string;
|
|
32
12
|
properties: string;
|
|
33
13
|
};
|
|
34
14
|
|
|
@@ -42,13 +22,5 @@ export const DEFAULT_TASK_DETAIL_LABELS: TaskDetailLabels = {
|
|
|
42
22
|
save: "Save",
|
|
43
23
|
saving: "Saving",
|
|
44
24
|
cancel: "Cancel",
|
|
45
|
-
comments: "Activity",
|
|
46
|
-
commentPlaceholder: "Leave a comment",
|
|
47
|
-
commentSend: "Comment",
|
|
48
|
-
commentEdit: "Edit comment",
|
|
49
|
-
commentDelete: "Delete comment",
|
|
50
|
-
commentEdited: "edited",
|
|
51
|
-
commentsEmpty: "No comments yet",
|
|
52
|
-
ai: "AI",
|
|
53
25
|
properties: "Properties",
|
|
54
26
|
};
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
5
|
import { Button } from "../../shadcn/button/index.js";
|
|
6
6
|
import { Textarea } from "../../shadcn/textarea/index.js";
|
|
7
|
+
import Thread from "../../thread/thread.svelte";
|
|
8
|
+
import type { ThreadComment, ThreadLabels, ThreadPost } from "../../thread/types.js";
|
|
7
9
|
import {
|
|
8
10
|
DEFAULT_TASK_DETAIL_LABELS,
|
|
9
|
-
type TaskComment,
|
|
10
11
|
type TaskDetailLabels,
|
|
11
12
|
} from "./detail-types.js";
|
|
12
|
-
import TaskThread from "./task-thread.svelte";
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
15
|
title: string;
|
|
@@ -21,22 +21,24 @@
|
|
|
21
21
|
onToggleDone?: () => void;
|
|
22
22
|
/** One 12px line under the title: origin, dates. */
|
|
23
23
|
meta?: Snippet;
|
|
24
|
-
/** The rail. A
|
|
24
|
+
/** The rail. A `PropertyRow layout="responsive"` with a `TaskChip` or any picker trigger in each. */
|
|
25
25
|
properties?: Snippet;
|
|
26
26
|
/** Under the rail's rows: ids, shortcuts, anything quiet. */
|
|
27
27
|
railFooter?: Snippet;
|
|
28
28
|
description?: string | null;
|
|
29
29
|
onDescriptionChange?: (next: string | null) => void | Promise<void>;
|
|
30
|
-
comments?:
|
|
30
|
+
comments?: ThreadComment[];
|
|
31
31
|
loadingComments?: boolean;
|
|
32
|
-
|
|
33
|
-
avatar?: Snippet<[
|
|
34
|
-
when?: (comment:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
/** Replaces the default avatar for a person or an agent, drawn in a 24px slot. */
|
|
33
|
+
avatar?: Snippet<[ThreadComment]>;
|
|
34
|
+
when?: (comment: ThreadComment) => string;
|
|
35
|
+
/** Returns false to keep the draft. Without it no composer shows. */
|
|
36
|
+
onPostComment?: (post: ThreadPost) => void | boolean | Promise<void | boolean>;
|
|
37
|
+
onEditComment?: (comment: ThreadComment, body: string) => void | Promise<void>;
|
|
38
|
+
onDeleteComment?: (comment: ThreadComment) => void | Promise<void>;
|
|
39
|
+
canEditComment?: (comment: ThreadComment) => boolean;
|
|
39
40
|
labels?: Partial<TaskDetailLabels>;
|
|
41
|
+
threadLabels?: Partial<ThreadLabels>;
|
|
40
42
|
class?: string;
|
|
41
43
|
}
|
|
42
44
|
|
|
@@ -53,14 +55,14 @@
|
|
|
53
55
|
onDescriptionChange,
|
|
54
56
|
comments = [],
|
|
55
57
|
loadingComments = false,
|
|
56
|
-
agentAvatar,
|
|
57
58
|
avatar,
|
|
58
59
|
when,
|
|
59
|
-
|
|
60
|
+
onPostComment,
|
|
60
61
|
onEditComment,
|
|
61
62
|
onDeleteComment,
|
|
62
63
|
canEditComment,
|
|
63
64
|
labels: labelOverrides,
|
|
65
|
+
threadLabels,
|
|
64
66
|
class: className,
|
|
65
67
|
}: Props = $props();
|
|
66
68
|
|
|
@@ -260,14 +262,13 @@
|
|
|
260
262
|
{/if}
|
|
261
263
|
</div>
|
|
262
264
|
|
|
263
|
-
<
|
|
265
|
+
<Thread
|
|
264
266
|
{comments}
|
|
265
267
|
loading={loadingComments}
|
|
266
|
-
labels={
|
|
267
|
-
{agentAvatar}
|
|
268
|
+
labels={threadLabels}
|
|
268
269
|
{avatar}
|
|
269
270
|
{when}
|
|
270
|
-
{
|
|
271
|
+
onPost={onPostComment}
|
|
271
272
|
onEdit={onEditComment}
|
|
272
273
|
onDelete={onDeleteComment}
|
|
273
274
|
canEdit={canEditComment}
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
AlertDialogTitle,
|
|
30
30
|
} from "../../shadcn/alert-dialog/index.js";
|
|
31
31
|
import { buttonVariants } from "../../shadcn/button/index.js";
|
|
32
|
+
import { copyText } from "../../components/copy/clipboard.js";
|
|
32
33
|
import {
|
|
33
34
|
Command,
|
|
34
35
|
CommandEmpty,
|
|
@@ -193,7 +194,7 @@
|
|
|
193
194
|
async function copy(kind: CopyKind, text: string) {
|
|
194
195
|
open = false;
|
|
195
196
|
// A blocked clipboard still reports, so the host can say the copy didn't land.
|
|
196
|
-
await
|
|
197
|
+
await copyText(text);
|
|
197
198
|
oncopy?.(kind, text);
|
|
198
199
|
}
|
|
199
200
|
|
package/src/work/docs/edited.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** The "Edited Oct 23" chip: how a stamp reads, and what to show when it can't be read. */
|
|
2
2
|
|
|
3
|
+
import { shortDate as monthDay } from "../../lib/dates.js";
|
|
4
|
+
|
|
3
5
|
export type DateInput = Date | string | number | null | undefined;
|
|
4
6
|
|
|
5
7
|
export type StampOptions = {
|
|
@@ -24,13 +26,8 @@ export function shortDate(value: DateInput, options: StampOptions = {}): string
|
|
|
24
26
|
const date = parseStamp(value);
|
|
25
27
|
if (!date) return "";
|
|
26
28
|
const { locale = "en-US", timeZone, now = new Date() } = options;
|
|
27
|
-
const
|
|
28
|
-
return
|
|
29
|
-
month: "short",
|
|
30
|
-
day: "numeric",
|
|
31
|
-
year: sameYear ? undefined : "numeric",
|
|
32
|
-
timeZone,
|
|
33
|
-
}).format(date);
|
|
29
|
+
const year = yearOf(date, options) !== yearOf(now, options);
|
|
30
|
+
return monthDay(date, { locale, timeZone, year });
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
/** "Oct 23, 14:44" for the last-edit row. Empty when the date can't be read. */
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import { TagChip } from "../../components/chip/index.js";
|
|
30
30
|
import { workLooks } from "../../components/work-parts/looks.js";
|
|
31
31
|
import { FolderSimple } from "phosphor-svelte";
|
|
32
|
+
import { Copied } from "../../components/copy/copied.svelte.js";
|
|
32
33
|
import type { InboxDraftEdits, InboxItem, InboxVerdict } from "./inbox-types.js";
|
|
33
34
|
|
|
34
35
|
interface Props {
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
let steering = $state(false);
|
|
79
80
|
let note = $state("");
|
|
80
81
|
let noteEl = $state<HTMLTextAreaElement | null>(null);
|
|
81
|
-
|
|
82
|
+
const copied = new Copied();
|
|
82
83
|
|
|
83
84
|
// WHY the selection is derived from the pick: a revised proposal brings new
|
|
84
85
|
// drafts, and the old pick may not exist in them. The agent's choice wins then.
|
|
@@ -158,13 +159,7 @@
|
|
|
158
159
|
async function copy() {
|
|
159
160
|
const draft = current();
|
|
160
161
|
if (!draft) return;
|
|
161
|
-
|
|
162
|
-
await navigator.clipboard.writeText(`${draft.subject}\n\n${draft.body}`);
|
|
163
|
-
copied = true;
|
|
164
|
-
setTimeout(() => (copied = false), 1600);
|
|
165
|
-
} catch {
|
|
166
|
-
copied = false;
|
|
167
|
-
}
|
|
162
|
+
if (!(await copied.copy(`${draft.subject}\n\n${draft.body}`, "", 1600))) copied.reset();
|
|
168
163
|
}
|
|
169
164
|
|
|
170
165
|
let sendLabel = $derived(
|
|
@@ -364,7 +359,7 @@
|
|
|
364
359
|
<Button size="sm" variant="ghost" onclick={steer} aria-keyshortcuts="s">Steer</Button>
|
|
365
360
|
{#if drafts.length}
|
|
366
361
|
<Button size="sm" variant="ghost" onclick={copy} class="ml-auto" aria-live="polite">
|
|
367
|
-
{copied ? "Copied" : "Copy"}
|
|
362
|
+
{copied.is() ? "Copied" : "Copy"}
|
|
368
363
|
</Button>
|
|
369
364
|
{/if}
|
|
370
365
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { relativeTime, shortDate } from "../../lib/dates.js";
|
|
1
2
|
import type { InboxDraft, InboxItem } from "./inbox-types.js";
|
|
2
3
|
|
|
3
4
|
/** The draft to open on: the agent's pick, else the first. */
|
|
@@ -64,12 +65,6 @@ export function freshReasoning(item: InboxItem, facts: CardFacts): string {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
const ISO = /^\d{4}-\d{2}-\d{2}T/;
|
|
67
|
-
const UNITS: [number, Intl.RelativeTimeFormatUnit][] = [
|
|
68
|
-
[604_800, "week"],
|
|
69
|
-
[86_400, "day"],
|
|
70
|
-
[3_600, "hour"],
|
|
71
|
-
[60, "minute"],
|
|
72
|
-
];
|
|
73
68
|
|
|
74
69
|
/**
|
|
75
70
|
* A time as a person says it: "3 hr. ago", "Oct 12". A host-formatted string stays as written,
|
|
@@ -79,16 +74,9 @@ export function whenOf(at: string, now: number = Date.now(), locale = "en-GB"):
|
|
|
79
74
|
if (!ISO.test(at)) return at;
|
|
80
75
|
const time = Date.parse(at);
|
|
81
76
|
if (Number.isNaN(time)) return "";
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return new Intl.DateTimeFormat(locale, { month: "short", day: "numeric" }).format(time);
|
|
86
|
-
}
|
|
87
|
-
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto", style: "short" });
|
|
88
|
-
for (const [size, unit] of UNITS) {
|
|
89
|
-
if (age >= size) return rtf.format(Math.round(seconds / size), unit);
|
|
90
|
-
}
|
|
91
|
-
return rtf.format(0, "second");
|
|
77
|
+
const age = Math.abs(Math.round((time - now) / 1000));
|
|
78
|
+
if (age >= 4 * 604_800) return shortDate(time, { locale });
|
|
79
|
+
return relativeTime(time, now, { locale, style: "short" });
|
|
92
80
|
}
|
|
93
81
|
|
|
94
82
|
/** True when a key press belongs to a text field and so isn't a shortcut. */
|
package/src/work/index.ts
CHANGED
|
@@ -23,11 +23,9 @@ export type {
|
|
|
23
23
|
TaskListRowContext,
|
|
24
24
|
} from "./task-types.js";
|
|
25
25
|
export { default as TaskDetail } from "./detail/task-detail.svelte";
|
|
26
|
-
export { default as TaskThread } from "./detail/task-thread.svelte";
|
|
27
|
-
export { default as TaskProperty } from "./detail/task-property.svelte";
|
|
28
26
|
export { default as TaskChip } from "./detail/task-chip.svelte";
|
|
29
27
|
export { DEFAULT_TASK_DETAIL_LABELS } from "./detail/detail-types.js";
|
|
30
|
-
export type {
|
|
28
|
+
export type { TaskDetailLabels } from "./detail/detail-types.js";
|
|
31
29
|
export { default as TimeTracker } from "./time/time-tracker.svelte";
|
|
32
30
|
export { default as TimeChip } from "./time/time-chip.svelte";
|
|
33
31
|
export { default as TimeRing } from "./time/time-ring.svelte";
|
|
@@ -65,11 +65,13 @@
|
|
|
65
65
|
</div>
|
|
66
66
|
|
|
67
67
|
<style>
|
|
68
|
-
/* Columns split the board's width equally, never under
|
|
69
|
-
nor over 420px (a 2-line card title stays near 55 characters a line).
|
|
68
|
+
/* Columns split the board's width equally, never under 240px (the board scrolls instead)
|
|
69
|
+
nor over 420px (a 2-line card title stays near 55 characters a line). At 280px four
|
|
70
|
+
columns needed 1168px, so a docs page at 1440 cut the Done column off; 240px fits four
|
|
71
|
+
in the 1008px a 1440 page leaves, and a card's meta row still fits on one line. */
|
|
70
72
|
.kanban-column {
|
|
71
73
|
flex: 1 1 0;
|
|
72
|
-
min-width: var(--kanban-column-min,
|
|
74
|
+
min-width: var(--kanban-column-min, 240px);
|
|
73
75
|
max-width: var(--kanban-column-max, 420px);
|
|
74
76
|
transition: opacity var(--duration-quick) var(--ease-smooth-out);
|
|
75
77
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { localDay as dayOf } from "../../lib/dates.js";
|
|
1
2
|
import type { Tone } from "../../lib/tone.js";
|
|
2
3
|
|
|
3
4
|
/** "90", "45m", "1h", "1.5h", "1h30", "1h 30m" and "1:30" to whole minutes. Null when it isn't a duration. */
|
|
@@ -35,9 +36,7 @@ export function formatClock(ms: number): { hours: string; minutes: string; secon
|
|
|
35
36
|
|
|
36
37
|
/** The reader's own calendar day as `YYYY-MM-DD`. */
|
|
37
38
|
export function localDay(date: Date = new Date()): string {
|
|
38
|
-
|
|
39
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
40
|
-
return `${date.getFullYear()}-${month}-${day}`;
|
|
39
|
+
return dayOf(date);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
/** "Today", "Yesterday", then "Mon, Oct 12", in the given locale. */
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as Icon } from "../../../src/components/icon/icon.svelte";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as LocaleSwitch } from "../../../src/components/locale-switch/locale-switch.svelte";
|
package/dist/icons/trades.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Trade icon map. Tabler covers the domain Lucide and Phosphor miss.
|
|
3
|
-
*/
|
|
4
|
-
/*
|
|
5
|
-
* Lucide/Phosphor gap audit (Sept 2026) against the 14 trade pills in the "NOT SURE? TAP WHAT YOU DO" strip. Lucide has `hammer`, `wrench`, `hard-hat`, `bolt`, `brick-wall`,
|
|
6
|
-
*/
|
|
7
|
-
/*
|
|
8
|
-
* `building`, `car`, but no `trowel`, no tiling grid, no `window` cleaning `spray/squeegee`, no `crane`, no dedicated `handyman` toolset. Phosphor has `hammer`, `wrench`, `hard-hat`, `paint-roller`, and also no `trowel`.
|
|
9
|
-
*/
|
|
10
|
-
/*
|
|
11
|
-
* Tabler 3.46 (6184 icons, MIT) has all 14 at a consistent 2px stroke on 24×24: droplet/pipe, bolt, home (roof), tools, key, window,
|
|
12
|
-
*/
|
|
13
|
-
/*
|
|
14
|
-
* car-garage, hammer / wave-saw-tool, helmet, trowel, grid-dots, leaf, car, crane. Svelte 5 port is ` @tabler/icons-svelte-runes` (runes, not the Svelte 4 ` @tabler/icons-svelte`).
|
|
15
|
-
*/
|
|
16
|
-
import { IconBolt, IconCar, IconCarGarage, IconCrane, IconDroplet, IconGridDots, IconHammer, IconHelmet, IconHome, IconKey, IconLeaf, IconTools, IconTrowel, IconWindow } from "@tabler/icons-svelte-runes";
|
|
17
|
-
export const tradeIconMap = {
|
|
18
|
-
plumbers: IconDroplet,
|
|
19
|
-
electricians: IconBolt,
|
|
20
|
-
roofers: IconHome,
|
|
21
|
-
handymen: IconTools,
|
|
22
|
-
locksmiths: IconKey,
|
|
23
|
-
"window-cleaners": IconWindow,
|
|
24
|
-
garages: IconCarGarage,
|
|
25
|
-
carpenters: IconHammer,
|
|
26
|
-
builders: IconHelmet,
|
|
27
|
-
plasterers: IconTrowel,
|
|
28
|
-
tilers: IconGridDots,
|
|
29
|
-
gardeners: IconLeaf,
|
|
30
|
-
"mobile-mechanics": IconCar,
|
|
31
|
-
"construction-companies": IconCrane
|
|
32
|
-
};
|
|
33
|
-
export const tradeLabels = {
|
|
34
|
-
plumbers: "PLUMBERS",
|
|
35
|
-
electricians: "ELECTRICIANS",
|
|
36
|
-
roofers: "ROOFERS",
|
|
37
|
-
handymen: "HANDYMEN",
|
|
38
|
-
locksmiths: "LOCKSMITHS",
|
|
39
|
-
"window-cleaners": "WINDOW CLEANERS",
|
|
40
|
-
garages: "GARAGES",
|
|
41
|
-
carpenters: "CARPENTERS",
|
|
42
|
-
builders: "BUILDERS",
|
|
43
|
-
plasterers: "PLASTERERS",
|
|
44
|
-
tilers: "TILERS",
|
|
45
|
-
gardeners: "GARDENERS",
|
|
46
|
-
"mobile-mechanics": "MOBILE MECHANICS",
|
|
47
|
-
"construction-companies": "CONSTRUCTION COMPANIES"
|
|
48
|
-
};
|
|
49
|
-
export const tradeSlugs = Object.keys(tradeIconMap);
|
package/dist/status/pill-tint.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// A quiet tint and no border: the icon and the text carry the tone, the fill only groups
|
|
2
|
-
// them. Neutral reads off the foreground, not `bg-muted`, because muted sits 0.005 L from
|
|
3
|
-
// the canvas and vanishes there (tokens board, "what fights the new scale").
|
|
4
|
-
// It lives in a .ts module, not status-pill.svelte's module script, so a plain tsc consumer
|
|
5
|
-
// can import it: tsc's `*.svelte` shim only knows a default export.
|
|
6
|
-
export const PILL_TINT = {
|
|
7
|
-
neutral: "bg-foreground/6 text-muted-foreground",
|
|
8
|
-
accent: "bg-[color-mix(in_oklab,var(--tone-accent,var(--color-primary))_12%,transparent)] text-[color:var(--tone-accent,var(--color-primary))]",
|
|
9
|
-
info: "bg-info/12 text-info-800 dark:text-info-400",
|
|
10
|
-
success: "bg-success/12 text-success-800 dark:text-success-400",
|
|
11
|
-
warning: "bg-warning/14 text-warning-800 dark:text-warning-400",
|
|
12
|
-
destructive: "bg-destructive/10 text-destructive-ink"
|
|
13
|
-
};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { defineStatuses } from "../../status/define-statuses.js";
|
|
2
|
-
/**
|
|
3
|
-
* An example status map for a sales pipeline. Names are neutral on purpose:
|
|
4
|
-
* swap the keys and labels for your own domain.
|
|
5
|
-
*/
|
|
6
|
-
export const salesStatuses = defineStatuses({
|
|
7
|
-
new: {
|
|
8
|
-
label: "New",
|
|
9
|
-
tone: "accent",
|
|
10
|
-
variant: "open"
|
|
11
|
-
},
|
|
12
|
-
contacted: {
|
|
13
|
-
label: "Contacted",
|
|
14
|
-
tone: "info",
|
|
15
|
-
variant: "open"
|
|
16
|
-
},
|
|
17
|
-
proposal: {
|
|
18
|
-
label: "Proposal",
|
|
19
|
-
tone: "accent",
|
|
20
|
-
variant: "progress",
|
|
21
|
-
progress: .25
|
|
22
|
-
},
|
|
23
|
-
negotiation: {
|
|
24
|
-
label: "Negotiation",
|
|
25
|
-
tone: "warning",
|
|
26
|
-
variant: "progress",
|
|
27
|
-
progress: .75
|
|
28
|
-
},
|
|
29
|
-
won: {
|
|
30
|
-
label: "Won",
|
|
31
|
-
tone: "success",
|
|
32
|
-
variant: "completed"
|
|
33
|
-
},
|
|
34
|
-
lost: {
|
|
35
|
-
label: "Lost",
|
|
36
|
-
tone: "destructive",
|
|
37
|
-
variant: "lost"
|
|
38
|
-
}
|
|
39
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import type { Snippet } from "svelte";
|
|
3
|
-
|
|
4
|
-
export interface ConfirmationAcceptedProps {
|
|
5
|
-
children?: Snippet;
|
|
6
|
-
}
|
|
7
|
-
</script>
|
|
8
|
-
|
|
9
|
-
<script lang="ts">
|
|
10
|
-
import { getConfirmationContext } from "./confirmation-context.svelte.js";
|
|
11
|
-
|
|
12
|
-
let { children }: ConfirmationAcceptedProps = $props();
|
|
13
|
-
|
|
14
|
-
const ctx = getConfirmationContext();
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
{#if ctx.kind === "accepted"}
|
|
18
|
-
<div
|
|
19
|
-
class="flex flex-col gap-2 animate-in fade-in slide-in-from-top-1 duration-300 motion-reduce:animate-none"
|
|
20
|
-
>
|
|
21
|
-
{@render children?.()}
|
|
22
|
-
</div>
|
|
23
|
-
{/if}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import type { Snippet } from "svelte";
|
|
3
|
-
|
|
4
|
-
export interface ConfirmationFailedProps {
|
|
5
|
-
children?: Snippet;
|
|
6
|
-
}
|
|
7
|
-
</script>
|
|
8
|
-
|
|
9
|
-
<script lang="ts">
|
|
10
|
-
import { getConfirmationContext } from "./confirmation-context.svelte.js";
|
|
11
|
-
|
|
12
|
-
let { children }: ConfirmationFailedProps = $props();
|
|
13
|
-
|
|
14
|
-
const ctx = getConfirmationContext();
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
{#if ctx.kind === "failed"}
|
|
18
|
-
<div
|
|
19
|
-
class="flex flex-col gap-2 animate-in fade-in slide-in-from-top-1 duration-300 motion-reduce:animate-none"
|
|
20
|
-
>
|
|
21
|
-
{@render children?.()}
|
|
22
|
-
</div>
|
|
23
|
-
{/if}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import type { Snippet } from "svelte";
|
|
3
|
-
|
|
4
|
-
export interface ConfirmationRejectedProps {
|
|
5
|
-
children?: Snippet;
|
|
6
|
-
}
|
|
7
|
-
</script>
|
|
8
|
-
|
|
9
|
-
<script lang="ts">
|
|
10
|
-
import { getConfirmationContext } from "./confirmation-context.svelte.js";
|
|
11
|
-
|
|
12
|
-
let { children }: ConfirmationRejectedProps = $props();
|
|
13
|
-
|
|
14
|
-
const ctx = getConfirmationContext();
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
{#if ctx.kind === "rejected"}
|
|
18
|
-
<div
|
|
19
|
-
class="flex flex-col gap-2 animate-in fade-in slide-in-from-top-1 duration-300 motion-reduce:animate-none"
|
|
20
|
-
>
|
|
21
|
-
{@render children?.()}
|
|
22
|
-
</div>
|
|
23
|
-
{/if}
|