@celestia-island/hikari 0.55.149 → 0.55.151

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.
Files changed (64) hide show
  1. package/package.json +1 -1
  2. package/src/animation/registerAnimations.ts +0 -1
  3. package/src/components/HkConnectionStatus.shell.test.tsx +34 -0
  4. package/src/components/HkConnectionStatus.tsx +31 -10
  5. package/src/components/HkKanban.tsx +10 -0
  6. package/src/components/HkScrollContainer.dock.test.tsx +89 -0
  7. package/src/components/HkScrollContainer.scss +50 -0
  8. package/src/components/HkScrollContainer.tsx +47 -0
  9. package/src/components/HkThemeDecor.test.tsx +11 -22
  10. package/src/components/HkThemeDecor.tsx +1 -1
  11. package/src/components/StatusTrayDecor.mount.test.ts +35 -0
  12. package/src/components/{HkStatusTray.size.test.ts → StatusTrayDecor.size.test.ts} +7 -3
  13. package/src/components/{HkStatusTray.test.tsx → StatusTrayDecor.test.tsx} +3 -2
  14. package/src/components/themeDecorLayout.contract.test.ts +6 -6
  15. package/src/composables/engineHealthTypes.ts +50 -0
  16. package/src/i18n/usageSurface.test.ts +0 -1
  17. package/src/index.ts +2 -102
  18. package/src/theme/standardDecor.test.ts +14 -13
  19. package/src/theme/standardDecor.ts +7 -3
  20. package/src/theme/themeDecorSurface.test.ts +5 -3
  21. package/src/utils/format.ts +0 -11
  22. package/src/components/HkAttachmentModal.scss +0 -49
  23. package/src/components/HkAttachmentModal.tsx +0 -318
  24. package/src/components/HkChatTypes.ts +0 -65
  25. package/src/components/HkContextRing.scss +0 -136
  26. package/src/components/HkContextRing.test.tsx +0 -309
  27. package/src/components/HkContextRing.tsx +0 -276
  28. package/src/components/HkModelCatalog.test.ts +0 -81
  29. package/src/components/HkModelCatalog.ts +0 -330
  30. package/src/components/HkModelTag.scss +0 -171
  31. package/src/components/HkModelTag.test.tsx +0 -155
  32. package/src/components/HkModelTag.tsx +0 -219
  33. package/src/components/HkRichInput.scss +0 -178
  34. package/src/components/HkRichInput.tsx +0 -335
  35. package/src/components/HkStatusTray.scss +0 -36
  36. package/src/components/HkStatusTray.tsx +0 -187
  37. package/src/components/HkTokenUsageBadge.scss +0 -50
  38. package/src/components/HkTokenUsageBadge.test.tsx +0 -59
  39. package/src/components/HkTokenUsageBadge.tsx +0 -54
  40. package/src/components/HkTokenUsagePanel.scss +0 -100
  41. package/src/components/HkTokenUsagePanel.tsx +0 -140
  42. package/src/components/HkToolBlock.scss +0 -327
  43. package/src/components/HkToolBlock.tsx +0 -394
  44. package/src/components/HkToolBlock.windowScroll.contract.test.ts +0 -93
  45. package/src/components/HkVoiceInputPopup.scss +0 -82
  46. package/src/components/HkVoiceInputPopup.tsx +0 -97
  47. package/src/composables/useAttachments.ts +0 -154
  48. package/src/composables/useConnectionProbe.test.ts +0 -179
  49. package/src/composables/useConnectionProbe.ts +0 -201
  50. package/src/composables/useEngineHealth.test.ts +0 -128
  51. package/src/composables/useEngineHealth.ts +0 -93
  52. package/src/composables/useSendShortcut.ts +0 -35
  53. package/src/i18n/contextKeys.test.ts +0 -52
  54. package/src/i18n/locales/ar/chat.json +0 -49
  55. package/src/i18n/locales/de/chat.json +0 -49
  56. package/src/i18n/locales/en/chat.json +0 -49
  57. package/src/i18n/locales/es/chat.json +0 -49
  58. package/src/i18n/locales/fr/chat.json +0 -49
  59. package/src/i18n/locales/ja/chat.json +0 -49
  60. package/src/i18n/locales/ko/chat.json +0 -49
  61. package/src/i18n/locales/pt/chat.json +0 -49
  62. package/src/i18n/locales/ru/chat.json +0 -49
  63. package/src/i18n/locales/zh-Hans/chat.json +0 -49
  64. package/src/i18n/locales/zh-Hant/chat.json +0 -49
package/src/index.ts CHANGED
@@ -124,30 +124,7 @@ export { default as HWaterfall } from "./components/HkWaterfall";
124
124
  export { default as HBlankCanvas } from "./components/HkBlankCanvas";
125
125
  export type { BlankCanvasSize } from "./components/HkBlankCanvas";
126
126
  export { default as HKanban } from "./components/HkKanban";
127
- export type {
128
- KanbanAxis,
129
- KanbanCardSlotProps,
130
- KanbanHeaderSlotProps,
131
- KanbanMove,
132
- } from "./components/HkKanban";
133
127
  export { default as HNodeCanvas, NODE_CANVAS_DEFAULTS } from "./components/HkNodeCanvas";
134
- export type {
135
- NodeCanvasCamera,
136
- NodeCanvasBounds,
137
- NodeCanvasSlotProps,
138
- MinimapPlacement,
139
- NodeCanvasEdge,
140
- EdgeRouting,
141
- EdgeSide,
142
- EdgeSubpath,
143
- EdgePointerEvent,
144
- } from "./components/HkNodeCanvas";
145
- export type {
146
- WaterfallBucket,
147
- WaterfallCardSlotProps,
148
- WaterfallHeaderSlotProps,
149
- WaterfallRailSlotProps,
150
- } from "./components/HkWaterfall";
151
128
  export { default as HDateTimePicker } from "./components/HkDateTimePicker";
152
129
  export { default as HDatePicker } from "./components/HkDatePicker";
153
130
  export { default as HTimeline } from "./components/HkTimeline";
@@ -251,7 +228,6 @@ export { type TrendPen, type TrendPoint, type AlarmThresholds } from "./componen
251
228
 
252
229
  // ── Theme decor (theme-provided decorative widgets, host-placed) ──────────
253
230
  export { default as HkThemeDecor, type HkThemeDecorSize } from "./components/HkThemeDecor";
254
- export { default as HkStatusTray, type HkStatusTraySize } from "./components/HkStatusTray";
255
231
 
256
232
  // Theme system
257
233
  export {
@@ -446,10 +422,6 @@ export { useApproachEnd } from "./composables/useApproachEnd";
446
422
  export type { ApproachEndOptions, ApproachEndHandle } from "./composables/useApproachEnd";
447
423
  export { useDeferredTransition } from "./composables/useDeferredTransition";
448
424
  export { useSurfaceTransition } from "./composables/useSurfaceTransition";
449
- export type {
450
- SurfaceTransition,
451
- SurfaceTransitionHooks,
452
- } from "./composables/useSurfaceTransition";
453
425
  export { useSizeMorph } from "./composables/useSizeMorph";
454
426
  export type { SizeMorph } from "./composables/useSizeMorph";
455
427
  export { useImage } from "./composables/useImage";
@@ -471,11 +443,6 @@ export {
471
443
  nearestLaidAncestor,
472
444
  placePoint,
473
445
  } from "./composables/layoutGeometry";
474
- export type {
475
- FrameMetrics,
476
- LayoutOffset,
477
- LayoutRect,
478
- } from "./composables/layoutGeometry";
479
446
  export { useMeasuredHighlight } from "./composables/useMeasuredHighlight";
480
447
  export type { UseMeasuredHighlightOptions, MeasuredHighlight } from "./composables/useMeasuredHighlight";
481
448
  export {
@@ -532,10 +499,6 @@ export {
532
499
  export { usePageTitle, useRouteTitle } from "./composables/usePageTitle";
533
500
  export { provideActionBar, useActionBar } from "./composables/useActionBar";
534
501
  export type { ActionBarRenderer } from "./composables/useActionBar";
535
- export { setProbeEndpoint, useConnectionProbe } from "./composables/useConnectionProbe";
536
- export type { ProbeResult } from "./composables/useConnectionProbe";
537
- export { useEngineHealth } from "./composables/useEngineHealth";
538
- export type { EngineHealth, EngineNetworkInfo } from "./composables/useEngineHealth";
539
502
  export { leadingZeroBits, sha256, solvePow, solvePowSync, verifyPow } from "./utils/pow";
540
503
  export type { PowChallenge, PowSolution } from "./utils/pow";
541
504
  export { HkLocalePicker as HLocalePicker } from "./components/HkLocalePicker";
@@ -570,13 +533,6 @@ export {
570
533
  } from "./components/HkSettingsDialog";
571
534
 
572
535
  export { HkCookieConsent as HCookieConsent } from "./components/HkCookieConsent";
573
- export {
574
- HkAttachmentModal as HAttachmentModal,
575
- previewKindFor,
576
- type HAttachmentDetail,
577
- type HAttachmentItem,
578
- type HAttachmentPreviewType,
579
- } from "./components/HkAttachmentModal";
580
536
  export { HkStatusBar as HStatusBar } from "./components/HkStatusBar";
581
537
  export { useConnectionInfo } from "./components/HkConnectionInfo";
582
538
  export type { ConnectionStateInput, HkConnectionInfo } from "./components/HkConnectionInfo";
@@ -587,67 +543,22 @@ export {
587
543
  type HkBackendStatus,
588
544
  type HkConnectionProbeSource,
589
545
  } from "./components/HkConnectionStatus";
546
+ export { HK_ENGINE_HEALTH } from "./components/HkConnectionStatus";
547
+ export type { ProbeResult, EngineHealth, EngineNetworkInfo } from "./composables/engineHealthTypes";
590
548
 
591
549
  // ── Chat kit (upstreamed from shittim-chest's plana-legacy layer) ──────────
592
- export { HkRichInput as HRichInput } from "./components/HkRichInput";
593
- export { HkVoiceInputPopup as HVoiceInputPopup } from "./components/HkVoiceInputPopup";
594
- export {
595
- HkToolBlock as HToolBlock,
596
- parseToolCallText,
597
- extractExecCode,
598
- buildHighlightedLines,
599
- type HParsedToolCall,
600
- type HToolBlockVariant,
601
- type HHighlightedLine,
602
- } from "./components/HkToolBlock";
603
550
  export {
604
551
  HkJsonTree as HJsonTree,
605
552
  buildJsonTree,
606
553
  type HJsonNode,
607
554
  } from "./components/HkJsonTree";
608
- export { HkTokenUsageBadge as HTokenUsageBadge } from "./components/HkTokenUsageBadge";
609
- export { HkTokenUsagePanel as HTokenUsagePanel } from "./components/HkTokenUsagePanel";
610
- export { HkModelTag as HModelTag } from "./components/HkModelTag";
611
- export { HkContextRing as HContextRing, HkContextRing, type HkContextSegment } from "./components/HkContextRing";
612
- export {
613
- getModelMeta,
614
- registerModelCatalog,
615
- splitModelId,
616
- type HModelPricing,
617
- type HModelMeta,
618
- type HModelCatalog,
619
- } from "./components/HkModelCatalog";
620
- export type {
621
- HChatRole,
622
- HToolCall,
623
- HToolCallStatus,
624
- HVoicePopupMode,
625
- HVoiceState,
626
- HModelUsageEntry,
627
- HModelCosts,
628
- } from "./components/HkChatTypes";
629
555
  // Canonical Hk* names for the directly-exported types above (2026-09-24
630
556
  // unification follow-up, second batch — caught by the third-scan reverse
631
557
  // validation). These types are defined with their H* names at the source.
632
- export type {
633
- HChatRole as HkChatRole,
634
- HToolCall as HkToolCall,
635
- HToolCallStatus as HkToolCallStatus,
636
- HVoicePopupMode as HkVoicePopupMode,
637
- HVoiceState as HkVoiceState,
638
- HModelUsageEntry as HkModelUsageEntry,
639
- HModelCosts as HkModelCosts,
640
- } from "./components/HkChatTypes";
641
- export { useSendShortcut } from "./composables/useSendShortcut";
642
- export type { SendShortcutMode } from "./composables/useSendShortcut";
643
- export { useAttachments } from "./composables/useAttachments";
644
- export type { HkUploadedFile } from "./composables/useAttachments";
645
558
 
646
559
  export {
647
- formatTokenCount,
648
560
  formatNumber,
649
561
  formatBytes,
650
- formatPriceUsd,
651
562
  formatRelativeTime,
652
563
  formatDateTime,
653
564
  formatDate,
@@ -861,27 +772,16 @@ export { HkSettingsGroup as HkSettingsGroup } from "./components/HkSettingsDialo
861
772
  export { HkSettingsSub as HkSettingsSub } from "./components/HkSettingsDialog";
862
773
  export { HkSettingsHint as HkSettingsHint } from "./components/HkSettingsDialog";
863
774
  export { HkCookieConsent as HkCookieConsent } from "./components/HkCookieConsent";
864
- export { HkAttachmentModal as HkAttachmentModal } from "./components/HkAttachmentModal";
865
775
  export { HkStatusBar as HkStatusBar, hkMergeVersionDisplay, hkParseIdentity } from "./components/HkStatusBar";
866
776
  export type { HkVersionIdentity } from "./components/HkStatusBar";
867
777
  export { HkCountdownDigit as HkCountdownDigit } from "./components/HkCountdownDigit";
868
778
  export { HkConnectionStatus as HkConnectionStatus } from "./components/HkConnectionStatus";
869
- export { HkRichInput as HkRichInput } from "./components/HkRichInput";
870
- export { HkVoiceInputPopup as HkVoiceInputPopup } from "./components/HkVoiceInputPopup";
871
- export { HkToolBlock as HkToolBlock } from "./components/HkToolBlock";
872
779
  export { HkJsonTree as HkJsonTree } from "./components/HkJsonTree";
873
- export { HkTokenUsageBadge as HkTokenUsageBadge } from "./components/HkTokenUsageBadge";
874
- export { HkTokenUsagePanel as HkTokenUsagePanel } from "./components/HkTokenUsagePanel";
875
- export { HkModelTag as HkModelTag } from "./components/HkModelTag";
876
780
 
877
781
  // Canonical Hk* names for directly-exported types (2026-09-24
878
782
  // unification follow-up). These types were defined with their H*
879
783
  // names at the source and exported directly — not aliased from an Hk*
880
784
  // identifier — so the canonical Hk* form is an alias export here.
881
- export { type HAttachmentItem as HkAttachmentItem } from "./components/HkAttachmentModal";
882
- export { type HAttachmentDetail as HkAttachmentDetail } from "./components/HkAttachmentModal";
883
- export { type HModelMeta as HkModelMeta } from "./components/HkModelCatalog";
884
- export { type HModelPricing as HkModelPricing } from "./components/HkModelCatalog";
885
785
  export { type HAboutCredit as HkAboutCredit } from "./components/HkAboutModal";
886
786
  export { type HAboutLink as HkAboutLink } from "./components/HkAboutModal";
887
787
  export { type HAboutLinkFace as HkAboutLinkFace } from "./components/HkAboutModal";
@@ -5,7 +5,6 @@ import HkDivider from "../components/HkDivider";
5
5
  import HkEmptyState from "../components/HkEmptyState";
6
6
  import HkSkeleton from "../components/HkSkeleton";
7
7
  import HkSplash from "../components/HkSplash";
8
- import HkStatusTray from "../components/HkStatusTray";
9
8
  import { registerStandardThemeDecor, STANDARD_THEME_DECOR_SLOTS } from "./standardDecor";
10
9
  import {
11
10
  getThemeDecor,
@@ -24,7 +23,7 @@ import {
24
23
  */
25
24
 
26
25
  describe("registerStandardThemeDecor", () => {
27
- it("registers nothing at import, then fills the five standard slots", async () => {
26
+ it("registers nothing at import, then fills the four in-tree standard slots", async () => {
28
27
  // A fresh module graph, so this proves the registration happens in the
29
28
  // call and not in an earlier case of this file. The component is
30
29
  // imported from the SAME fresh graph: module identity is per graph, so
@@ -32,7 +31,8 @@ describe("registerStandardThemeDecor", () => {
32
31
  vi.resetModules();
33
32
  const themeDecor = await import("./themeDecor");
34
33
  const standardDecor = await import("./standardDecor");
35
- const freshTray = (await import("../components/HkStatusTray")).default;
34
+ // The tray moved to @celestia-island/default-theme; the builtin
35
+ // floor for status.tray is now EMPTY (pinned below).
36
36
 
37
37
  // Importing this module is not a side effect (same contract as
38
38
  // standardGroups): only the call populates the registry.
@@ -43,15 +43,14 @@ describe("registerStandardThemeDecor", () => {
43
43
  expect([...themeDecor.themeDecorSlots()].sort()).toEqual(
44
44
  [...STANDARD_THEME_DECOR_SLOTS].sort(),
45
45
  );
46
- expect(themeDecor.getThemeDecor("status.tray", "any-theme")?.component).toBe(freshTray);
46
+ expect(themeDecor.getThemeDecor("status.tray", "any-theme")).toBeUndefined();
47
47
  });
48
48
 
49
49
  it("resolves every built-in for any theme id (floor level, and the declared list matches)", () => {
50
50
  registerStandardThemeDecor();
51
51
 
52
52
  const expected: [string, unknown][] = [
53
- ["status.tray", HkStatusTray],
54
- ["placeholder.screen", HkSplash],
53
+ ["placeholder.screen", HkSplash],
55
54
  ["placeholder.section", HkSkeleton],
56
55
  ["empty", HkEmptyState],
57
56
  ["divider", HkDivider],
@@ -93,28 +92,30 @@ describe("registerStandardThemeDecor", () => {
93
92
 
94
93
  // A theme's own implementation wins over the built-in …
95
94
  const themed = defineComponent({ name: "ThemedTray", setup: () => () => h("i") });
96
- registerThemeDecor({ themeId: "endfield", slot: "status.tray", component: themed });
95
+ registerThemeDecor({ themeId: "endfield", slot: "placeholder.screen", component: themed });
97
96
  registerStandardThemeDecor();
98
- expect(getThemeDecor("status.tray", "endfield")?.component).toBe(themed);
99
- expect(getThemeDecor("status.tray", "other")?.component).toBe(HkStatusTray);
97
+ expect(getThemeDecor("placeholder.screen", "endfield")?.component).toBe(themed);
98
+ // A slot with no theme registration keeps the builtin floor.
99
+ expect(getThemeDecor("placeholder.screen", "other")?.component).toBe(HkSplash);
100
100
 
101
101
  // … and so does a host-wide `"*"` registration.
102
102
  const hostWide = defineComponent({ name: "HostTray", setup: () => () => h("i") });
103
- registerThemeDecor({ themeId: "*", slot: "status.tray", component: hostWide });
103
+ registerThemeDecor({ themeId: "*", slot: "empty", component: hostWide });
104
104
  registerStandardThemeDecor();
105
- expect(getThemeDecor("status.tray", "other")?.component).toBe(hostWide);
105
+ expect(getThemeDecor("empty", "other")?.component).toBe(hostWide);
106
106
  });
107
107
 
108
108
  it("lands in the registry via initTheme() (and not before)", async () => {
109
109
  vi.resetModules();
110
110
  const themeDecor = await import("./themeDecor");
111
- const freshTray = (await import("../components/HkStatusTray")).default;
111
+ // The tray moved to @celestia-island/default-theme; the builtin
112
+ // floor for status.tray is now EMPTY (pinned below).
112
113
  const useTheme = await import("./useTheme");
113
114
  expect(themeDecor.getThemeDecor("status.tray", "default")).toBeUndefined();
114
115
 
115
116
  useTheme.initTheme();
116
117
 
117
- expect(themeDecor.getThemeDecor("status.tray", "default")?.component).toBe(freshTray);
118
+ expect(themeDecor.getThemeDecor("status.tray", "default")).toBeUndefined();
118
119
  // initTheme starts the solar theme clock; stop it so this file leaves no
119
120
  // timer behind.
120
121
  useTheme.stopThemeClock();
@@ -2,7 +2,6 @@ import HkDivider from "../components/HkDivider";
2
2
  import HkEmptyState from "../components/HkEmptyState";
3
3
  import HkSkeleton from "../components/HkSkeleton";
4
4
  import HkSplash from "../components/HkSplash";
5
- import HkStatusTray from "../components/HkStatusTray";
6
5
  import { registerThemeDecorBuiltin, type ThemeDecorSlot } from "./themeDecor";
7
6
 
8
7
  /**
@@ -15,6 +14,13 @@ import { registerThemeDecorBuiltin, type ThemeDecorSlot } from "./themeDecor";
15
14
  * consumer that never calls `initTheme()` (tests, SSR, a host with its own
16
15
  * theme engine) gets an untouched registry.
17
16
  *
17
+ * The status.tray slot is deliberately UNREGISTERED here since the
18
+ * chat-kit demotion wave: hikari's former brand tray moved to the
19
+ * @celestia-island/default-theme package (the reference precompiled
20
+ * theme), which registers it on the "*" wildcard — the same override
21
+ * path any custom theme takes. A host that mounts nothing gets an
22
+ * empty tray slot (the documented "no backdrop" pattern).
23
+ *
18
24
  * These land on the BUILT-IN floor of the decor registry, below both an
19
25
  * exact theme id and the `"*"` wildcard: a theme (or a host) that registers
20
26
  * its own implementation for a slot replaces hikari's default without
@@ -36,7 +42,6 @@ import { registerThemeDecorBuiltin, type ThemeDecorSlot } from "./themeDecor";
36
42
  * gap is a decision on record, not an oversight.
37
43
  */
38
44
  export const STANDARD_THEME_DECOR_SLOTS: readonly ThemeDecorSlot[] = [
39
- "status.tray",
40
45
  "placeholder.screen",
41
46
  "placeholder.section",
42
47
  "empty",
@@ -56,7 +61,6 @@ let registered = false;
56
61
  export function registerStandardThemeDecor(): void {
57
62
  if (registered) return;
58
63
  registered = true;
59
- registerThemeDecorBuiltin({ slot: "status.tray", component: HkStatusTray });
60
64
  registerThemeDecorBuiltin({ slot: "placeholder.screen", component: HkSplash });
61
65
  registerThemeDecorBuiltin({ slot: "placeholder.section", component: HkSkeleton });
62
66
  registerThemeDecorBuiltin({ slot: "empty", component: HkEmptyState });
@@ -56,11 +56,13 @@ describe("theme decor public surface", () => {
56
56
  expect(typeof themeBarrel.getThemeDecor).toBe("function");
57
57
  expect(typeof themeBarrel.registerStandardThemeDecor).toBe("function");
58
58
  expect(themeBarrel.themeDecorVersion.value).toBeTypeOf("number");
59
- expect(themeBarrel.STANDARD_THEME_DECOR_SLOTS).toContain("status.tray");
59
+ // status.tray moved to @celestia-island/default-theme (the
60
+ // demotion wave) — the builtin floor no longer carries it.
61
+ expect(themeBarrel.STANDARD_THEME_DECOR_SLOTS).not.toContain("status.tray");
60
62
 
61
63
  // The components ride the root barrel, where every other component lives.
62
64
  expect((barrel.HkThemeDecor as { name?: string }).name).toBe("HkThemeDecor");
63
- expect((barrel.HkStatusTray as { name?: string }).name).toBe("HkStatusTray");
65
+ expect((barrel.HkSplash as { name?: string }).name).toBe("HkSplash");
64
66
  });
65
67
 
66
68
  it("registers nothing just because the surface was imported", () => {
@@ -77,6 +79,6 @@ describe("theme decor public surface", () => {
77
79
 
78
80
  // …and the extractor does detect a component import where one exists.
79
81
  const seamImports = importSpecifiers("standardDecor.ts");
80
- expect(seamImports.filter((s) => s.includes("components/")).length).toBeGreaterThanOrEqual(5);
82
+ expect(seamImports.filter((s) => s.includes("components/")).length).toBeGreaterThanOrEqual(4);
81
83
  });
82
84
  });
@@ -15,11 +15,6 @@
15
15
  import { activeLocale } from "../i18n/context";
16
16
 
17
17
  /** "1234" -> "1.2k", "2500000" -> "2.5M". */
18
- export function formatTokenCount(n: number): string {
19
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
20
- if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
21
- return String(n);
22
- }
23
18
 
24
19
  /** "1234" -> "1.2k". */
25
20
  export function formatNumber(n: number): string {
@@ -37,12 +32,6 @@ export function formatBytes(bytes: number): string {
37
32
  }
38
33
 
39
34
  /** USD amount -> "$0.10" / "$1.5" / "$120". Negative/NaN clamp to 0. */
40
- export function formatPriceUsd(n: number, currency = "$"): string {
41
- if (!Number.isFinite(n) || n < 0) n = 0;
42
- if (n >= 100) return `${currency}${Math.round(n)}`;
43
- if (n >= 1) return `${currency}${n.toFixed(1)}`;
44
- return `${currency}${n.toFixed(2)}`;
45
- }
46
35
 
47
36
  /** Timestamp -> "Just now" / "5m ago" / "3h ago" / "2d ago" / "2w ago" /
48
37
  * locale date. */
@@ -1,49 +0,0 @@
1
- .s-attachment-modal {
2
- display: flex;
3
- flex-direction: column;
4
- gap: var(--space-12, 0.75rem);
5
- }
6
-
7
- .s-attachment-modal-text {
8
- max-height: 48vh;
9
- padding: var(--space-12, 0.75rem);
10
- border: 1px solid var(--border-faint, rgb(var(--color-border) / 10%));
11
- border-radius: var(--radius-md, 8px);
12
- background: rgb(var(--color-surface) / 0.5);
13
- }
14
-
15
- .s-attachment-modal-text-empty {
16
- margin: 0;
17
- font-size: var(--text-xs, 0.75rem);
18
- color: rgb(var(--color-muted));
19
- font-style: italic;
20
- }
21
-
22
- .s-attachment-modal-text-error {
23
- margin: var(--space-8, 0.5rem) 0 0;
24
- font-size: var(--text-xs, 0.75rem);
25
- color: rgb(var(--color-error));
26
- }
27
-
28
- .s-attachment-modal-file {
29
- display: flex;
30
- flex-direction: column;
31
- align-items: center;
32
- gap: var(--space-8, 0.5rem);
33
- padding: var(--space-24, 1.5rem);
34
- color: rgb(var(--color-muted));
35
- }
36
-
37
- .s-attachment-modal-file-name {
38
- margin: 0;
39
- font-size: var(--text-sm, 0.8125rem);
40
- max-width: 100%;
41
- overflow: hidden;
42
- text-overflow: ellipsis;
43
- white-space: nowrap;
44
- }
45
-
46
- .s-attachment-modal-meta {
47
- display: flex;
48
- gap: var(--space-8, 0.5rem);
49
- }