@groeponline/pi-wishcraft 1.1.0 → 1.2.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/CHANGELOG.md +13 -0
- package/README.md +16 -7
- package/ROADMAP.md +41 -260
- package/docs/commands.md +6 -2
- package/docs/configuration.md +21 -0
- package/docs/design/accessibility.md +71 -0
- package/docs/design/deck-layout.md +69 -0
- package/docs/design/motion-gallery.md +80 -0
- package/docs/design/motion-system.md +119 -0
- package/docs/design/presets.md +196 -0
- package/docs/design/regression-testing.md +54 -0
- package/docs/design/responsive.md +53 -0
- package/docs/design/signal.md +68 -0
- package/docs/design/theme-contract.md +132 -0
- package/docs/design/vnext-overview.md +55 -0
- package/docs/design/vnext-release-plan.md +212 -0
- package/docs/index.md +19 -1
- package/docs/segments.md +1 -1
- package/package.json +2 -1
- package/src/config/appearance.ts +230 -0
- package/src/config/parse.ts +41 -0
- package/src/config/presets.ts +184 -0
- package/src/config/structural-presets.ts +555 -0
- package/src/config/tokens.ts +154 -0
- package/src/config/types.ts +210 -2
- package/src/extension/commands/commands.ts +36 -12
- package/src/extension/commands/powerline-completions.ts +4 -0
- package/src/extension/commands/queue-commands.ts +6 -0
- package/src/extension/core/segment-context.ts +7 -1
- package/src/extension/core/state.ts +14 -0
- package/src/extension/core/types.ts +7 -0
- package/src/extension/session/session-lifecycle.ts +20 -0
- package/src/extension/settings/appearance-write.ts +135 -0
- package/src/extension/settings/wishcraft-config-items.ts +119 -0
- package/src/extension/settings/wishcraft-config.ts +28 -114
- package/src/extension/skills/skill-manager.ts +5 -0
- package/src/extension/ui/deck/component.ts +358 -0
- package/src/extension/ui/deck/index.ts +34 -0
- package/src/extension/ui/deck/render.ts +260 -0
- package/src/extension/ui/deck/route-bodies.ts +209 -0
- package/src/extension/ui/deck/routes.ts +37 -0
- package/src/extension/ui/deck/session-snapshot.ts +104 -0
- package/src/extension/ui/deck/types.ts +92 -0
- package/src/extension/ui/powerline-menu-view.ts +19 -15
- package/src/extension/ui/signal-layout.ts +77 -0
- package/src/extension/ui/status-line-renderers.ts +20 -2
- package/src/motion/accessibility.ts +59 -0
- package/src/motion/catalog-extra.ts +429 -0
- package/src/motion/catalog.ts +336 -0
- package/src/motion/composer.ts +147 -0
- package/src/motion/frames.ts +84 -0
- package/src/motion/gallery.ts +77 -0
- package/src/motion/index.ts +78 -0
- package/src/motion/policy.ts +128 -0
- package/src/motion/scheduler.ts +159 -0
- package/src/motion/types.ts +132 -0
- package/src/render/timer.ts +1 -0
- package/src/signal/controller.ts +123 -0
- package/src/signal/integration.ts +42 -0
- package/src/signal/render.ts +178 -0
- package/src/theme/detect.ts +48 -0
- package/src/theme/tokens/index.ts +9 -0
- package/src/theme/tokens/mapping.ts +62 -0
- package/src/theme/tokens/types.ts +39 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three-lane Signal renderer.
|
|
3
|
+
*
|
|
4
|
+
* Left: model/repository state. Center: live semantic activity and motion rail.
|
|
5
|
+
* Right: context/queue/usage. Modules retain the existing per-segment fault
|
|
6
|
+
* isolation because every item still renders through `renderSegment`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
10
|
+
import type {
|
|
11
|
+
PresetDef,
|
|
12
|
+
SegmentContext,
|
|
13
|
+
SignalSpec,
|
|
14
|
+
StatusLineSegmentId,
|
|
15
|
+
StatusLineSeparatorStyle,
|
|
16
|
+
} from "../config/types.ts";
|
|
17
|
+
import { mergeSegmentsWithCustomItems } from "../config/powerline-config.ts";
|
|
18
|
+
import { renderSegment } from "../segments/index.ts";
|
|
19
|
+
import { ansi, colorEnabled, getFgAnsiCode } from "../theme/colors.ts";
|
|
20
|
+
import { getSeparator } from "../theme/separators.ts";
|
|
21
|
+
import { frameAt, getMotion } from "../motion/index.ts";
|
|
22
|
+
import type { SignalRuntime } from "./controller.ts";
|
|
23
|
+
|
|
24
|
+
export interface SignalRenderOptions {
|
|
25
|
+
separatorStyle: StatusLineSeparatorStyle;
|
|
26
|
+
signal: SignalSpec;
|
|
27
|
+
ascii?: boolean;
|
|
28
|
+
layout?: import("../config/types.ts").StatusLineLayout | null;
|
|
29
|
+
disabledSegments?: StatusLineSegmentId[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Module {
|
|
33
|
+
content: string;
|
|
34
|
+
width: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function renderSignal(
|
|
38
|
+
ctx: SegmentContext,
|
|
39
|
+
preset: PresetDef,
|
|
40
|
+
runtime: SignalRuntime,
|
|
41
|
+
availableWidth: number,
|
|
42
|
+
options: SignalRenderOptions,
|
|
43
|
+
): { topContent: string; secondaryContent: string } {
|
|
44
|
+
const merged = mergeSegmentsWithCustomItems(
|
|
45
|
+
preset,
|
|
46
|
+
ctx.effectiveCustomItems,
|
|
47
|
+
{
|
|
48
|
+
layout: options.layout ?? null,
|
|
49
|
+
disabledSegments: options.disabledSegments ?? [],
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
const left = renderModules(merged.leftSegments, ctx);
|
|
53
|
+
const right = renderModules(merged.rightSegments, ctx);
|
|
54
|
+
const secondary = renderModules(merged.secondarySegments, ctx);
|
|
55
|
+
const separator = getSeparator(options.separatorStyle).left;
|
|
56
|
+
|
|
57
|
+
const center = renderActivity(runtime, options.signal, options.ascii);
|
|
58
|
+
const top = fitLanes(left, center, right, separator, availableWidth);
|
|
59
|
+
return {
|
|
60
|
+
topContent: top,
|
|
61
|
+
secondaryContent: joinModules(secondary, separator, availableWidth),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function renderModules(ids: readonly StatusLineSegmentId[], ctx: SegmentContext): Module[] {
|
|
66
|
+
const modules: Module[] = [];
|
|
67
|
+
for (const id of ids) {
|
|
68
|
+
// renderSegment is the established per-module error boundary.
|
|
69
|
+
const rendered = renderSegment(id, ctx);
|
|
70
|
+
if (!rendered.visible || !rendered.content) continue;
|
|
71
|
+
modules.push({
|
|
72
|
+
content: rendered.content,
|
|
73
|
+
width: visibleWidth(rendered.content),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return modules;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function renderActivity(
|
|
80
|
+
runtime: SignalRuntime,
|
|
81
|
+
spec: SignalSpec,
|
|
82
|
+
ascii = false,
|
|
83
|
+
): string {
|
|
84
|
+
const def = getMotion(runtime.motionId) ?? getMotion(spec.animation);
|
|
85
|
+
const glyph = def
|
|
86
|
+
? frameAt(def, runtime.tick, ascii)
|
|
87
|
+
: ascii
|
|
88
|
+
? "*"
|
|
89
|
+
: "◆";
|
|
90
|
+
const label = runtime.activity || "ready";
|
|
91
|
+
const open = spec.caps.leftOpen ?? "";
|
|
92
|
+
const close = spec.caps.leftClose ?? "";
|
|
93
|
+
const dim = getFgAnsiCode("sep");
|
|
94
|
+
const reset = colorEnabled() ? ansi.reset : "";
|
|
95
|
+
const rail = runtime.active
|
|
96
|
+
? `${spec.separators.left}${glyph}${spec.separators.right}`
|
|
97
|
+
: `${spec.separators.left}${spec.separators.right}`;
|
|
98
|
+
return `${dim}${open}${rail}${close}${reset} ${label}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function fitLanes(
|
|
102
|
+
leftInput: Module[],
|
|
103
|
+
center: string,
|
|
104
|
+
rightInput: Module[],
|
|
105
|
+
separator: string,
|
|
106
|
+
availableWidth: number,
|
|
107
|
+
): string {
|
|
108
|
+
if (availableWidth <= 0) return "";
|
|
109
|
+
const left = [...leftInput];
|
|
110
|
+
const right = [...rightInput];
|
|
111
|
+
const sep = styledSeparator(separator);
|
|
112
|
+
const centerWidth = visibleWidth(center);
|
|
113
|
+
|
|
114
|
+
const lane = (modules: Module[]) => modules.map((module) => module.content).join(sep);
|
|
115
|
+
const totalWidth = () => {
|
|
116
|
+
const leftText = lane(left);
|
|
117
|
+
const rightText = lane(right);
|
|
118
|
+
return (
|
|
119
|
+
visibleWidth(leftText) +
|
|
120
|
+
visibleWidth(rightText) +
|
|
121
|
+
centerWidth +
|
|
122
|
+
(leftText ? 2 : 0) +
|
|
123
|
+
(rightText ? 2 : 0)
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Preserve the model/git side first, then context/queue. Remove the least
|
|
128
|
+
// important outer modules until all three lanes fit.
|
|
129
|
+
while (totalWidth() + 2 > availableWidth && right.length > 1) right.shift();
|
|
130
|
+
while (totalWidth() + 2 > availableWidth && left.length > 1) left.pop();
|
|
131
|
+
while (totalWidth() + 2 > availableWidth && right.length > 0) right.shift();
|
|
132
|
+
while (totalWidth() + 2 > availableWidth && left.length > 0) left.pop();
|
|
133
|
+
|
|
134
|
+
const leftText = lane(left);
|
|
135
|
+
const rightText = lane(right);
|
|
136
|
+
if (centerWidth + 2 > availableWidth) {
|
|
137
|
+
return ` ${truncatePlain(center, Math.max(0, availableWidth - 2))} `;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const contentWidth =
|
|
141
|
+
visibleWidth(leftText) +
|
|
142
|
+
visibleWidth(rightText) +
|
|
143
|
+
centerWidth +
|
|
144
|
+
(leftText ? 2 : 0) +
|
|
145
|
+
(rightText ? 2 : 0);
|
|
146
|
+
const flexible = Math.max(0, availableWidth - 2 - contentWidth);
|
|
147
|
+
const before = leftText ? " " : "";
|
|
148
|
+
const after = rightText ? " " : "";
|
|
149
|
+
// Extra space lives between center and right, keeping state metrics pinned.
|
|
150
|
+
return ` ${leftText}${before}${center}${after}${" ".repeat(flexible)}${rightText} `;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function joinModules(modules: Module[], separator: string, width: number): string {
|
|
154
|
+
if (modules.length === 0) return "";
|
|
155
|
+
const sep = styledSeparator(separator);
|
|
156
|
+
const fitted: Module[] = [];
|
|
157
|
+
let used = 2;
|
|
158
|
+
for (const module of modules) {
|
|
159
|
+
const next = module.width + (fitted.length ? visibleWidth(separator) + 2 : 0);
|
|
160
|
+
if (used + next > width) break;
|
|
161
|
+
fitted.push(module);
|
|
162
|
+
used += next;
|
|
163
|
+
}
|
|
164
|
+
return fitted.length ? ` ${fitted.map((module) => module.content).join(sep)} ` : "";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function styledSeparator(separator: string): string {
|
|
168
|
+
const color = getFgAnsiCode("sep");
|
|
169
|
+
const reset = colorEnabled() ? ansi.reset : "";
|
|
170
|
+
return ` ${color}${separator}${reset} `;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function truncatePlain(text: string, width: number): string {
|
|
174
|
+
if (width <= 0) return "";
|
|
175
|
+
// Activity labels are ASCII; strip ANSI before this emergency narrow path.
|
|
176
|
+
const plain = text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
177
|
+
return plain.length <= width ? plain : plain.slice(0, Math.max(0, width - 1)) + "…";
|
|
178
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal and host capability detection. Pure: reads an env map, no process
|
|
3
|
+
* globals except the default argument. Motion policy is assembled elsewhere
|
|
4
|
+
* so this file stays free of the motion engine.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface TerminalEnvironment {
|
|
8
|
+
noColor: boolean;
|
|
9
|
+
lowColor: boolean;
|
|
10
|
+
screenReader: boolean;
|
|
11
|
+
reducedMotion: boolean;
|
|
12
|
+
dumb: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const MOTION_LEVELS = ["full", "reduced", "functional", "off"] as const;
|
|
16
|
+
|
|
17
|
+
export function detectEnvironment(
|
|
18
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
19
|
+
): TerminalEnvironment {
|
|
20
|
+
const term = (env.TERM ?? "").toLowerCase();
|
|
21
|
+
const colorterm = (env.COLORTERM ?? "").toLowerCase();
|
|
22
|
+
const noColor = env.NO_COLOR !== undefined && env.NO_COLOR !== "";
|
|
23
|
+
const dumb = term === "dumb" || term === "";
|
|
24
|
+
const lowColor =
|
|
25
|
+
!colorterm.includes("truecolor") &&
|
|
26
|
+
!colorterm.includes("24bit") &&
|
|
27
|
+
(term.includes("vt100") || term === "linux" || /\bansi\b/.test(term));
|
|
28
|
+
const screenReader =
|
|
29
|
+
env.WISHCRAFT_SCREEN_READER === "1" ||
|
|
30
|
+
env.ACCESSIBILITY_SCREEN_READER === "1" ||
|
|
31
|
+
env.NVDA !== undefined ||
|
|
32
|
+
env.VOICEOVER === "1";
|
|
33
|
+
const reducedMotion =
|
|
34
|
+
env.WISHCRAFT_REDUCED_MOTION === "1" ||
|
|
35
|
+
env.PREFER_REDUCED_MOTION === "1" ||
|
|
36
|
+
env.MOZ_PREFER_REDUCED_MOTION === "1";
|
|
37
|
+
|
|
38
|
+
return { noColor, lowColor: lowColor && !noColor, screenReader, reducedMotion, dumb };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function motionLevelFromEnv(
|
|
42
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
43
|
+
): (typeof MOTION_LEVELS)[number] | null {
|
|
44
|
+
const raw = (env.WISHCRAFT_MOTION ?? "").toLowerCase();
|
|
45
|
+
return (MOTION_LEVELS as readonly string[]).includes(raw)
|
|
46
|
+
? (raw as (typeof MOTION_LEVELS)[number])
|
|
47
|
+
: null;
|
|
48
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/theme/tokens/index.ts
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* Public exports for Wishcraft Semantic Design Tokens.
|
|
5
|
+
* ---------------------------------------------------------------------------
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export * from "./types.ts";
|
|
9
|
+
export * from "./mapping.ts";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/theme/tokens/mapping.ts
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* Default token mapping and legacy SemanticColor bridge for Wishcraft.
|
|
5
|
+
* ---------------------------------------------------------------------------
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ColorScheme, SemanticColor } from "../../config/types.ts";
|
|
9
|
+
import type { WishcraftTokens } from "./types.ts";
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_TOKENS: WishcraftTokens = {
|
|
12
|
+
surface: "#0f172a",
|
|
13
|
+
surfaceRaised: "#1e293b",
|
|
14
|
+
text: "#f8fafc",
|
|
15
|
+
textMuted: "dim",
|
|
16
|
+
primary: "#f59e0b",
|
|
17
|
+
secondary: "#00afaf",
|
|
18
|
+
accent: "#ea580c",
|
|
19
|
+
success: "success",
|
|
20
|
+
warning: "warning",
|
|
21
|
+
error: "error",
|
|
22
|
+
focus: "#38bdf8",
|
|
23
|
+
selection: "#334155",
|
|
24
|
+
motionDim: "dim",
|
|
25
|
+
motionHot: "#fbbf24",
|
|
26
|
+
motionTrail: "#78350f",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Derives legacy SemanticColor configuration from modern WishcraftTokens.
|
|
31
|
+
*/
|
|
32
|
+
export function deriveColorSchemeFromTokens(tokens: WishcraftTokens): Required<ColorScheme> {
|
|
33
|
+
return {
|
|
34
|
+
model: tokens.primary,
|
|
35
|
+
shellMode: tokens.accent,
|
|
36
|
+
path: tokens.secondary,
|
|
37
|
+
gitClean: tokens.success,
|
|
38
|
+
gitDirty: tokens.warning,
|
|
39
|
+
thinking: "thinkingOff",
|
|
40
|
+
thinkingMinimal: "thinkingMinimal",
|
|
41
|
+
thinkingLow: "thinkingLow",
|
|
42
|
+
thinkingMedium: "thinkingMedium",
|
|
43
|
+
context: tokens.textMuted,
|
|
44
|
+
contextWarn: tokens.warning,
|
|
45
|
+
contextError: tokens.error,
|
|
46
|
+
cost: tokens.text,
|
|
47
|
+
tokens: tokens.textMuted,
|
|
48
|
+
queue: tokens.accent,
|
|
49
|
+
separator: tokens.textMuted,
|
|
50
|
+
border: tokens.surfaceRaised,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates WishcraftTokens by merging overrides onto defaults.
|
|
56
|
+
*/
|
|
57
|
+
export function createTokens(overrides?: Partial<WishcraftTokens>): WishcraftTokens {
|
|
58
|
+
return {
|
|
59
|
+
...DEFAULT_TOKENS,
|
|
60
|
+
...overrides,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/theme/tokens/types.ts
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* Semantic Design Tokens for Wishcraft (Crush pattern).
|
|
5
|
+
* ---------------------------------------------------------------------------
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
export type ColorValue = ThemeColor | `#${string}`;
|
|
11
|
+
|
|
12
|
+
export interface WishcraftTokens {
|
|
13
|
+
// Surfaces & Backgrounds
|
|
14
|
+
surface: ColorValue;
|
|
15
|
+
surfaceRaised: ColorValue;
|
|
16
|
+
|
|
17
|
+
// Typography
|
|
18
|
+
text: ColorValue;
|
|
19
|
+
textMuted: ColorValue;
|
|
20
|
+
|
|
21
|
+
// Brand & Accents
|
|
22
|
+
primary: ColorValue;
|
|
23
|
+
secondary: ColorValue;
|
|
24
|
+
accent: ColorValue;
|
|
25
|
+
|
|
26
|
+
// Feedback & State
|
|
27
|
+
success: ColorValue;
|
|
28
|
+
warning: ColorValue;
|
|
29
|
+
error: ColorValue;
|
|
30
|
+
|
|
31
|
+
// Interaction
|
|
32
|
+
focus: ColorValue;
|
|
33
|
+
selection: ColorValue;
|
|
34
|
+
|
|
35
|
+
// Motion & Animation Channels
|
|
36
|
+
motionDim: ColorValue;
|
|
37
|
+
motionHot: ColorValue;
|
|
38
|
+
motionTrail: ColorValue;
|
|
39
|
+
}
|