dsh-claude-desktop-theme 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -140
- package/lib/client.js +1931 -400
- package/lib/index.js +38 -17
- package/lib/types/client/compat/conversation-process.d.ts +8 -0
- package/lib/types/client/compat/frame.d.ts +29 -0
- package/lib/types/client/compat/hero-chips.d.ts +9 -0
- package/lib/types/client/compat/host-dom.d.ts +6 -0
- package/lib/types/client/compat/mutation-scope.d.ts +43 -0
- package/lib/types/client/compat/pane-chrome.d.ts +2 -0
- package/lib/types/client/compat/reply-signature.d.ts +6 -0
- package/lib/types/client/compat/view-switch.d.ts +1 -1
- package/lib/types/client/contracts/ports.d.ts +2 -0
- package/lib/types/client/features/conversation/header-actions/HeaderActionButton.d.ts +2 -2
- package/lib/types/client/features/conversation/header-actions/entries.d.ts +11 -15
- package/lib/types/client/features/conversation/view-switch-icons.d.ts +2 -2
- package/lib/types/client/features/model-controls/popup.d.ts +20 -0
- package/lib/types/client/features/new-session/ripples.d.ts +2 -2
- package/lib/types/client/features/statistics/source.d.ts +2 -1
- package/package.json +1 -2
package/lib/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var DEFAULT_FONTS = Object.freeze({
|
|
|
24
24
|
});
|
|
25
25
|
var DEFAULT_VIEW = Object.freeze({
|
|
26
26
|
showEmptyGroups: false,
|
|
27
|
-
groupByStatus:
|
|
27
|
+
groupByStatus: true,
|
|
28
28
|
showHeaderSubagents: false,
|
|
29
29
|
showHeaderTeam: false
|
|
30
30
|
});
|
|
@@ -431,12 +431,14 @@ var UsageAggregator = class {
|
|
|
431
431
|
ctx;
|
|
432
432
|
warn;
|
|
433
433
|
values = /* @__PURE__ */ new Map();
|
|
434
|
-
queued =
|
|
434
|
+
queued = /* @__PURE__ */ new Map();
|
|
435
435
|
folding = /* @__PURE__ */ new Set();
|
|
436
436
|
failures = /* @__PURE__ */ new Map();
|
|
437
437
|
running = 0;
|
|
438
438
|
refreshRun = null;
|
|
439
439
|
snapshot = null;
|
|
440
|
+
dirty = true;
|
|
441
|
+
listed = /* @__PURE__ */ new Set();
|
|
440
442
|
disposed = false;
|
|
441
443
|
constructor(ctx, warn) {
|
|
442
444
|
this.ctx = ctx;
|
|
@@ -444,7 +446,15 @@ var UsageAggregator = class {
|
|
|
444
446
|
}
|
|
445
447
|
/** The most recent snapshot; an empty one before the first refresh. */
|
|
446
448
|
get current() {
|
|
447
|
-
|
|
449
|
+
if (this.snapshot === null || this.dirty) {
|
|
450
|
+
this.snapshot = aggregateUsage(this.values.values(), {
|
|
451
|
+
now: Date.now(),
|
|
452
|
+
pending: this.queued.size + this.running,
|
|
453
|
+
missed: this.failures.size
|
|
454
|
+
});
|
|
455
|
+
this.dirty = false;
|
|
456
|
+
}
|
|
457
|
+
return this.snapshot;
|
|
448
458
|
}
|
|
449
459
|
/**
|
|
450
460
|
* Bring the snapshot up to date.
|
|
@@ -456,6 +466,7 @@ var UsageAggregator = class {
|
|
|
456
466
|
* @returns the snapshot as of this pass.
|
|
457
467
|
*/
|
|
458
468
|
refresh() {
|
|
469
|
+
if (this.disposed) return Promise.resolve(this.current);
|
|
459
470
|
const running = this.refreshRun;
|
|
460
471
|
if (running !== null) return running;
|
|
461
472
|
const run = this.pass().finally(() => {
|
|
@@ -467,24 +478,34 @@ var UsageAggregator = class {
|
|
|
467
478
|
/** Stop folding; in-flight reads settle but no longer publish. */
|
|
468
479
|
dispose() {
|
|
469
480
|
this.disposed = true;
|
|
470
|
-
this.queued.
|
|
481
|
+
this.queued.clear();
|
|
482
|
+
this.values.clear();
|
|
483
|
+
this.failures.clear();
|
|
484
|
+
this.listed.clear();
|
|
485
|
+
this.snapshot = null;
|
|
471
486
|
}
|
|
472
487
|
async pass() {
|
|
473
488
|
try {
|
|
474
489
|
const candidates = await this.list();
|
|
490
|
+
if (this.disposed) return this.current;
|
|
475
491
|
const listed = new Set(candidates.map((candidate) => candidate.header.id));
|
|
492
|
+
this.listed = listed;
|
|
476
493
|
for (const id of [...this.values.keys()]) if (!listed.has(id)) this.values.delete(id);
|
|
477
494
|
for (const id of [...this.failures.keys()]) if (!listed.has(id)) this.failures.delete(id);
|
|
495
|
+
for (const id of this.queued.keys()) if (!listed.has(id)) this.queued.delete(id);
|
|
478
496
|
for (const candidate of candidates) {
|
|
479
497
|
if (this.disposed) break;
|
|
480
498
|
const value = this.readSafely(candidate);
|
|
481
499
|
if (value === void 0) this.enqueue(candidate);
|
|
482
|
-
else
|
|
500
|
+
else {
|
|
501
|
+
this.queued.delete(candidate.header.id);
|
|
502
|
+
this.values.set(candidate.header.id, value);
|
|
503
|
+
}
|
|
483
504
|
}
|
|
484
505
|
} catch (error) {
|
|
485
506
|
this.warn("session listing failed; the card keeps the last snapshot", error);
|
|
486
507
|
}
|
|
487
|
-
this.
|
|
508
|
+
this.invalidate();
|
|
488
509
|
return this.current;
|
|
489
510
|
}
|
|
490
511
|
/** Every stored session the card counts, newest first. */
|
|
@@ -530,24 +551,25 @@ var UsageAggregator = class {
|
|
|
530
551
|
/** Queue one cold fold unless it is already queued, running, or cooling down. */
|
|
531
552
|
enqueue(candidate) {
|
|
532
553
|
const id = candidate.header.id;
|
|
533
|
-
if (this.folding.has(id) || this.queued.
|
|
554
|
+
if (this.disposed || this.folding.has(id) || this.queued.has(id)) return;
|
|
534
555
|
const failedAt = this.failures.get(id);
|
|
535
556
|
if (failedAt !== void 0 && Date.now() - failedAt < RETRY_COOLDOWN_MS) return;
|
|
536
|
-
this.queued.
|
|
557
|
+
this.queued.set(id, candidate);
|
|
537
558
|
this.pump();
|
|
538
559
|
}
|
|
539
560
|
/** Keep at most {@link FOLD_CONCURRENCY} folds in flight. */
|
|
540
561
|
pump() {
|
|
541
562
|
while (!this.disposed && this.running < FOLD_CONCURRENCY) {
|
|
542
|
-
const candidate = this.queued.
|
|
563
|
+
const candidate = this.queued.values().next().value;
|
|
543
564
|
if (candidate === void 0) return;
|
|
544
565
|
const id = candidate.header.id;
|
|
566
|
+
this.queued.delete(id);
|
|
545
567
|
this.folding.add(id);
|
|
546
568
|
this.running += 1;
|
|
547
569
|
void this.fold(candidate).finally(() => {
|
|
548
570
|
this.folding.delete(id);
|
|
549
571
|
this.running -= 1;
|
|
550
|
-
this.
|
|
572
|
+
this.invalidate();
|
|
551
573
|
this.pump();
|
|
552
574
|
});
|
|
553
575
|
}
|
|
@@ -560,7 +582,9 @@ var UsageAggregator = class {
|
|
|
560
582
|
let handle;
|
|
561
583
|
try {
|
|
562
584
|
handle = await persistence.open(candidate.header.id, "read");
|
|
585
|
+
if (this.disposed || !this.listed.has(candidate.header.id)) return;
|
|
563
586
|
const { events } = await handle.read(0);
|
|
587
|
+
if (this.disposed || !this.listed.has(candidate.header.id)) return;
|
|
564
588
|
const block = cache.coldSnapshot(handle.header, handle.inheritedEventCount, events);
|
|
565
589
|
const value = block.values[USAGE_KEY];
|
|
566
590
|
if (this.disposed) return;
|
|
@@ -571,6 +595,7 @@ var UsageAggregator = class {
|
|
|
571
595
|
this.values.set(candidate.header.id, value);
|
|
572
596
|
this.failures.delete(candidate.header.id);
|
|
573
597
|
} catch (error) {
|
|
598
|
+
if (this.disposed || !this.listed.has(candidate.header.id)) return;
|
|
574
599
|
this.failures.set(candidate.header.id, Date.now());
|
|
575
600
|
this.warn(`fold failed for session ${candidate.header.id}`, error);
|
|
576
601
|
} finally {
|
|
@@ -582,14 +607,10 @@ var UsageAggregator = class {
|
|
|
582
607
|
}
|
|
583
608
|
}
|
|
584
609
|
}
|
|
585
|
-
/**
|
|
586
|
-
|
|
610
|
+
/** Fold completions invalidate totals; only a route read pays for aggregation. */
|
|
611
|
+
invalidate() {
|
|
587
612
|
if (this.disposed) return;
|
|
588
|
-
this.
|
|
589
|
-
now: Date.now(),
|
|
590
|
-
pending: this.queued.length + this.running,
|
|
591
|
-
missed: this.failures.size
|
|
592
|
-
});
|
|
613
|
+
this.dirty = true;
|
|
593
614
|
}
|
|
594
615
|
};
|
|
595
616
|
function mountStatistics(ctx, isEnabled) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Disposer } from '../contracts/ports.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Reveal process containers through the host's own handlers. Changing CSS alone
|
|
4
|
+
* leaves hidden reasoning and process members under the host's collapse policy.
|
|
5
|
+
* Leaf reasoning/tool disclosures keep their independent state. Stable hooks
|
|
6
|
+
* come from ui-chat's TurnProcessNodeView and ChatGroupSeat in DSH rc.2.
|
|
7
|
+
*/
|
|
8
|
+
export declare function mountConversationProcess(document: Document, report: (error: unknown) => void): Disposer;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coalesce DOM work into one paint frame.
|
|
3
|
+
*
|
|
4
|
+
* A mutation observer delivers one batch per microtask, and several documents
|
|
5
|
+
* (a panel drag, a streaming transcript, a slider writing its own inline
|
|
6
|
+
* style) can produce many batches between two paints. Doing a full geometry
|
|
7
|
+
* pass per batch multiplies the layout reads by the batch count; doing it once
|
|
8
|
+
* per frame is what the screen can actually use.
|
|
9
|
+
*
|
|
10
|
+
* The fallbacks are deliberate: an occluded Electron window throttles animation
|
|
11
|
+
* frames, and a frame without `requestAnimationFrame` at all (some test hosts)
|
|
12
|
+
* must still settle. In both cases the work runs on a timeout instead, so the
|
|
13
|
+
* published values are never left stale.
|
|
14
|
+
*/
|
|
15
|
+
/** One coalescing scheduler; `schedule` runs `task` once before the next paint. */
|
|
16
|
+
export interface FrameScheduler {
|
|
17
|
+
/** Queue the task; repeated calls before it runs collapse into one. */
|
|
18
|
+
schedule(): void;
|
|
19
|
+
/** Drop a queued task without running it. */
|
|
20
|
+
cancel(): void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build a scheduler over one task.
|
|
24
|
+
*
|
|
25
|
+
* @param task - the read/write pass to run at most once per frame.
|
|
26
|
+
* @param win - the window supplying animation frames; defaults to the global.
|
|
27
|
+
* @returns the scheduler; its task never runs after `cancel`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function frameScheduler(task: () => void, win?: Window | null): FrameScheduler;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Disposer } from '../contracts/ports.ts';
|
|
2
|
+
/** Measured hero row width, also reserved in the Composer tools group. */
|
|
3
|
+
export declare const HERO_CHIP_WIDTH_PROPERTY = "--ccd-workspace-seat";
|
|
4
|
+
/**
|
|
5
|
+
* Measure the complete workspace/preset row, whose chips size to their content.
|
|
6
|
+
* Reserve that width plus one gap in the tools group to separate the permission
|
|
7
|
+
* button. Observe label, font and viewport-driven size changes.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mountHeroChips(document: Document, report: (error: unknown) => void): Disposer;
|
|
@@ -163,6 +163,9 @@ export declare const HOST: Readonly<{
|
|
|
163
163
|
readonly browserList: '._7514NG_list';
|
|
164
164
|
/** FlatList is the only native body the status presentation reorders. */
|
|
165
165
|
readonly browserFlatList: '._7514NG_flatList';
|
|
166
|
+
/** StateDot's stable state attributes, used when root sessionStatus is absent. */
|
|
167
|
+
readonly sessionNeedsInput: '[aria-hidden="true"][data-state="warning"]';
|
|
168
|
+
readonly sessionWorking: '[aria-hidden="true"][data-state="ongoing"]';
|
|
166
169
|
/** Rows.module.css: one Workspace's row, the line the merged header shares,
|
|
167
170
|
and its trailing action group (`sidebar.css` parks both). */
|
|
168
171
|
readonly workspaceRow: '.jJkEga_projectRow';
|
|
@@ -347,6 +350,9 @@ export declare const HOST: Readonly<{
|
|
|
347
350
|
* means the frame is not the pinned one and the feature must stay off.
|
|
348
351
|
*/
|
|
349
352
|
export declare const ANCHOR: Readonly<{
|
|
353
|
+
/** ui-chat TurnProcessNodeView / ChatGroupSeat: process containers, not leaf details. */
|
|
354
|
+
readonly turnProcess: 'button[data-turn-process]';
|
|
355
|
+
readonly stepProcessToggle: '[data-step-process] > div > button[data-process-activity]';
|
|
350
356
|
readonly frame: '[data-slot="root"] > div';
|
|
351
357
|
readonly sidebar: '[data-slot="sidebar"]';
|
|
352
358
|
readonly workspaces: '[data-slot="sidebar.workspaces"]';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a mutation batch can concern one region of the page.
|
|
3
|
+
*
|
|
4
|
+
* Several modules here observe the whole body because the host can move or
|
|
5
|
+
* rebuild their anchor at any time, then pay a geometry pass on every batch.
|
|
6
|
+
* Most batches, though, come from somewhere else — a drag in the right pane, a
|
|
7
|
+
* streaming transcript on another page, another plugin writing its own markup.
|
|
8
|
+
* This predicate lets a module skip those batches without narrowing what it
|
|
9
|
+
* observes, so an anchor that is replaced still reaches it.
|
|
10
|
+
*
|
|
11
|
+
* The answer is deliberately conservative: a region that is missing, detached
|
|
12
|
+
* or cannot answer `contains` makes every batch relevant, so a module that
|
|
13
|
+
* cannot yet measure is never starved of the mutation that would let it.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Whether any record in a batch can move something inside `region`.
|
|
17
|
+
*
|
|
18
|
+
* `region` is the element the module measures — the composer, the scroller,
|
|
19
|
+
* the conversation root. A change inside it, on it, or on any of its ancestors
|
|
20
|
+
* counts, because those are the writes that can change its box. A `null`
|
|
21
|
+
* region, or one the host has already detached, makes the batch relevant:
|
|
22
|
+
* that is exactly the state in which the module has to look again.
|
|
23
|
+
*
|
|
24
|
+
* @param records - the observer's batch.
|
|
25
|
+
* @param region - the element being measured, or null while it is unknown.
|
|
26
|
+
* @returns true when the batch is worth a pass.
|
|
27
|
+
*/
|
|
28
|
+
export declare function recordsTouch(records: readonly MutationRecord[], region: Element | null): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a batch inserted a subtree the selector names.
|
|
31
|
+
*
|
|
32
|
+
* For modules that watch the whole body only because their anchor can be
|
|
33
|
+
* replaced: an insertion that matches the selector is the one structural event
|
|
34
|
+
* worth a pass, and it costs one `matches`/`querySelector` per inserted element
|
|
35
|
+
* rather than a scan of the document. The predicate is also conservative: a
|
|
36
|
+
* node from another realm, or one without the selector helpers, counts as a
|
|
37
|
+
* match.
|
|
38
|
+
*
|
|
39
|
+
* @param records - the observer's batch.
|
|
40
|
+
* @param selector - the marker whose arrival matters.
|
|
41
|
+
* @returns true when an inserted element is, or contains, a match.
|
|
42
|
+
*/
|
|
43
|
+
export declare function recordsIntroduce(records: readonly MutationRecord[], selector: string): boolean;
|
|
@@ -7,6 +7,7 @@ export interface PaneChromeOptions {
|
|
|
7
7
|
readonly onNew: () => void;
|
|
8
8
|
readonly onSelect: (id: string) => void;
|
|
9
9
|
readonly onClose: (id: string) => void;
|
|
10
|
+
readonly onCloseGroup?: () => void;
|
|
10
11
|
readonly closeTabs: readonly string[];
|
|
11
12
|
readonly terminals: readonly {
|
|
12
13
|
id: string;
|
|
@@ -16,6 +17,7 @@ export interface PaneChromeOptions {
|
|
|
16
17
|
readonly files: readonly {
|
|
17
18
|
id: string;
|
|
18
19
|
title: string;
|
|
20
|
+
path: string;
|
|
19
21
|
selected: boolean;
|
|
20
22
|
}[];
|
|
21
23
|
readonly panel: HTMLElement;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Disposer } from '../contracts/ports.ts';
|
|
2
|
+
/** The host marks only the latest turn ending in a reply as always revealed.
|
|
3
|
+
* Starting another turn clears that marker before its response is complete. */
|
|
4
|
+
export declare const REPLY_SIGNATURE_SEAT = "[data-turn-tail][data-actions-reveal=\"always\"]:has(> [data-clock=\"end\"])";
|
|
5
|
+
/** Keep one playful signature at the latest settled reply, following host swaps. */
|
|
6
|
+
export declare function mountReplySignature(document: Document, report: (error: unknown) => void): Disposer;
|
|
@@ -21,7 +21,7 @@ export declare const VIEW_GLYPH_ATTRIBUTE = "data-ccd-view-glyph";
|
|
|
21
21
|
/**
|
|
22
22
|
* The parked pill's inset from the sidebar column's right edge, in px. Measured
|
|
23
23
|
* at @2x on the reference: the column's divider sits 16 device px right of the
|
|
24
|
-
* pill's right edge (
|
|
24
|
+
* pill's right edge (DESIGN.md §6.5).
|
|
25
25
|
*/
|
|
26
26
|
export declare const VIEW_DOCK_INSET = 8;
|
|
27
27
|
/** Bundled artwork, one per shipped View; see features/conversation/view-switch-icons.ts. */
|
|
@@ -82,6 +82,8 @@ export interface SidebarRightPort {
|
|
|
82
82
|
}): void;
|
|
83
83
|
/** Focus a tab and the pane holding it; a missing tab is left alone. */
|
|
84
84
|
focus(tabId: string): void;
|
|
85
|
+
/** Close a tab in the on-screen Session through the host's cleanup path. */
|
|
86
|
+
close?(tabId: string): void;
|
|
85
87
|
/**
|
|
86
88
|
* Open tab metadata across adopted Sessions — and, in the shipped face, the
|
|
87
89
|
* snapshot store behind it: the plugin's pane layer re-reads the layout
|
|
@@ -11,8 +11,8 @@ export interface HeaderActionProps {
|
|
|
11
11
|
/**
|
|
12
12
|
* One view button for the conversation header.
|
|
13
13
|
*
|
|
14
|
-
* All three sit in the utilities group beside the native actions,
|
|
15
|
-
* project folder; the corner seat past them holds nothing (the panel's shipped
|
|
14
|
+
* All three sit in the utilities group beside the native actions, in terminal,
|
|
15
|
+
* browser, project folder order; the corner seat past them holds nothing (the panel's shipped
|
|
16
16
|
* expand control is retired there and its action moves into the Session menu).
|
|
17
17
|
* All three are the same control: the box size, hover surface and spacing
|
|
18
18
|
* belong to the seat's own stylesheet
|
|
@@ -18,28 +18,24 @@ export declare const FILES_KIND = "files";
|
|
|
18
18
|
/**
|
|
19
19
|
* List orders inside `conversation.session.header.utilities`.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* scheduled-tasks entry (-5), not only below this plugin's own views: the
|
|
24
|
-
* reference draws the folder as the cluster's first control, whatever a Session
|
|
25
|
-
* happens to carry. The two views then land directly before the native session
|
|
26
|
-
* menu at 0 and keep it as the cluster's closing entry.
|
|
21
|
+
* Terminal, browser and project folder sit directly before the native session
|
|
22
|
+
* menu at 0, in that order.
|
|
27
23
|
*/
|
|
28
|
-
export declare const
|
|
29
|
-
export declare const
|
|
30
|
-
export declare const
|
|
24
|
+
export declare const TERMINAL_ORDER = -3;
|
|
25
|
+
export declare const BROWSER_ORDER = -2;
|
|
26
|
+
export declare const FOLDER_ORDER = -1;
|
|
31
27
|
/** The views this plugin exposes in the utilities group, in cluster order. */
|
|
32
28
|
export declare const VIEWS: readonly [{
|
|
33
|
-
readonly id: 'ccd-folder';
|
|
34
|
-
readonly kind: "files";
|
|
35
|
-
readonly order: -11;
|
|
36
|
-
}, {
|
|
37
29
|
readonly id: 'ccd-terminal';
|
|
38
30
|
readonly kind: 'terminal';
|
|
39
|
-
readonly order: -
|
|
31
|
+
readonly order: -3;
|
|
40
32
|
}, {
|
|
41
33
|
readonly id: 'ccd-browser';
|
|
42
34
|
readonly kind: 'browser';
|
|
35
|
+
readonly order: -2;
|
|
36
|
+
}, {
|
|
37
|
+
readonly id: 'ccd-folder';
|
|
38
|
+
readonly kind: "files";
|
|
43
39
|
readonly order: -1;
|
|
44
40
|
}];
|
|
45
41
|
/**
|
|
@@ -64,7 +60,7 @@ export type OpenViewKind = (kind: string, sessionId: string) => void;
|
|
|
64
60
|
export declare function utilitiesEntry(view: (typeof VIEWS)[number], labels: Record<(typeof VIEWS)[number]['kind'] | typeof FILES_KIND, string>, open: OpenViewKind): {
|
|
65
61
|
name: "conversation.session.header.utilities";
|
|
66
62
|
id: "ccd-browser" | "ccd-folder" | "ccd-terminal";
|
|
67
|
-
order: -
|
|
63
|
+
order: -3 | -2 | -1;
|
|
68
64
|
inject: (sessionId: string) => HeaderActionProps;
|
|
69
65
|
};
|
|
70
66
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The two View switcher glyphs, bundled as trusted static markup.
|
|
3
3
|
*
|
|
4
|
-
* Drawn to the reference's own segments (
|
|
5
|
-
*
|
|
4
|
+
* Drawn to the reference's own segments (DESIGN.md §6.5, @2x): a pair of
|
|
5
|
+
* speech bubbles for the chat View and the `</>` pair for the
|
|
6
6
|
* trajectory View, both on the CCD 16 grid — 1–1.2px stroke (each glyph's own
|
|
7
7
|
* weight read off the reference), round caps and joins, `currentColor` ink,
|
|
8
8
|
* ink box ≈ 12.5 × 11px inside the 16px artwork.
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
|
+
interface Size {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The panel's border box, kept between placements.
|
|
8
|
+
*
|
|
9
|
+
* Its size changes only when its own content does, which the resize observer
|
|
10
|
+
* below already reports, so each placement can read the trigger alone instead
|
|
11
|
+
* of forcing a second layout of the surface. The first read is the seeding one:
|
|
12
|
+
* the panel has been shown by the time `place()` runs, so its box is real.
|
|
13
|
+
*
|
|
14
|
+
* @param surface - the popover element.
|
|
15
|
+
* @returns the cached size, and a way to re-read it when the observer fires.
|
|
16
|
+
*/
|
|
17
|
+
export declare function cachedSize(surface: Element): {
|
|
18
|
+
current: () => Size;
|
|
19
|
+
refresh: () => void;
|
|
20
|
+
};
|
|
2
21
|
/** Viewport-clamped top-layer surface; focus and observers die with the panel. */
|
|
3
22
|
export declare function usePopup(anchor: RefObject<HTMLButtonElement>, close: () => void): {
|
|
4
23
|
panel: RefObject<HTMLDivElement>;
|
|
@@ -8,3 +27,4 @@ export declare function usePopup(anchor: RefObject<HTMLButtonElement>, close: ()
|
|
|
8
27
|
visibility: 'hidden' | 'visible';
|
|
9
28
|
};
|
|
10
29
|
};
|
|
30
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { HeroRippleImpulse } from '../../contracts/hero-canvas.ts';
|
|
2
|
-
/** One
|
|
2
|
+
/** One cell's independently changing character and moving light, before edge fade. */
|
|
3
3
|
export declare function rippleCell(x: number, y: number, elapsed: number, width: number, height: number, impulses?: readonly HeroRippleImpulse[]): {
|
|
4
4
|
alpha: number;
|
|
5
5
|
glyph: string;
|
|
6
6
|
};
|
|
7
|
-
/**
|
|
7
|
+
/** Shimmering ASCII rows lit by an autonomous, slowly swelling wave field. */
|
|
8
8
|
export declare function drawHeroRipples(canvas: HTMLCanvasElement, elapsed: number, impulses?: readonly HeroRippleImpulse[]): void;
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* are still being folded (`pending > 0`) the card is genuinely incomplete, so
|
|
11
11
|
* it refreshes every couple of seconds; once the snapshot is complete it drops
|
|
12
12
|
* to a slow poll. A missing route (the host half is off) hides the card rather
|
|
13
|
-
* than failing the mount.
|
|
13
|
+
* than failing the mount. Hidden documents pause reads and resume immediately
|
|
14
|
+
* when visible, keeping background windows from repeatedly aggregating history.
|
|
14
15
|
*/
|
|
15
16
|
import { type StatsSnapshot } from '../../../shared/stats.ts';
|
|
16
17
|
import type { Logger } from '../../contracts/ports.ts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-claude-desktop-theme",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Claude Code Desktop-style theme for DeepSeek Harness (DSH) Desktop on macOS and Windows, with light/dark themes, model/effort pickers and an interactive pixel whale",
|
|
6
6
|
"author": "zkforge",
|
|
@@ -85,7 +85,6 @@
|
|
|
85
85
|
"check": "npm run typecheck && npm run check:architecture && npm test && npm run build && npm run check:package",
|
|
86
86
|
"prepack": "npm run check",
|
|
87
87
|
"pack:local": "npm pack --pack-destination artifacts",
|
|
88
|
-
"preview:install": "node scripts/install-local.mjs",
|
|
89
88
|
"pet:gifs": "node scripts/pet-gifs.mjs",
|
|
90
89
|
"effort:gifs": "node scripts/effort-gif.mjs"
|
|
91
90
|
},
|