Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ repos:

- id: no-console-log
name: no console.log in production code
entry: "bash -c 'for f in \"$@\"; do case \"$f\" in *.test.*|*/testing/*|*/e2e/*|scripts/*) continue;; esac; if grep -n \"^\\s*console\\.log\\b\" \"$f\" >/dev/null 2>&1; then echo \"$f has console.log:\"; grep -n \"^\\s*console\\.log\\b\" \"$f\"; exit 1; fi; done'"
entry: "bash -c 'for f in \"$@\"; do case \"$f\" in *.test.*|*/testing/*|*/e2e/*|scripts/*|*/scripts/*) continue;; esac; if grep -n \"^\\s*console\\.log\\b\" \"$f\" >/dev/null 2>&1; then echo \"$f has console.log:\"; grep -n \"^\\s*console\\.log\\b\" \"$f\"; exit 1; fi; done'"
language: system
types_or: [javascript, jsx, ts, tsx]

Expand Down
2 changes: 1 addition & 1 deletion Claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

## Naming

- **Domain types are plain nouns.** `Glyph`, `Contour`, `Point`, `Anchor` — not `GlyphData`, `GlyphInfo`, `GlyphState`, `GlyphRenderData`. If you need a modifier, it should describe the _kind_ of thing (`EditableGlyph`, `RenderContour`), not append generic suffixes.
- **Domain types are plain nouns.** `Glyph`, `Contour`, `Point`, `Anchor` — not `Glyph`, `GlyphInfo`, `GlyphState`, `GlyphRenderData`. If you need a modifier, it should describe the _kind_ of thing (`EditableGlyph`, `RenderContour`), not append generic suffixes.
- **Avoid `-Data`, `-Info`, `-State` suffixes** on types unless it genuinely represents transient mutable state (e.g. `TextRunRenderState` for a signal value consumed by a render pass). If the type represents a domain concept, name it after the concept.

## Roadmap
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/main/docs/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ src/main/
## Key Types

- `ThemeName` -- `"light" | "dark" | "system"`, stored in `MenuManager.currentTheme`
- `DebugState` -- aggregates `reactScanEnabled`, `debugPanelOpen`, and `DebugOverlays`; only used in dev builds
- `Debug` -- aggregates `reactScanEnabled`, `debugPanelOpen`, and `DebugOverlays`; only used in dev builds
- `DebugOverlays` -- per-overlay booleans (`tightBounds`, `hitRadii`, `segmentBounds`, `glyphBbox`)
- `IpcCommands` -- renderer-to-main request/response channels (invoke/handle)
- `IpcEvents` -- main-to-renderer broadcast channels (send/on)
Expand Down Expand Up @@ -110,5 +110,5 @@ IPC handlers are split across managers. `WindowManager` registers window-control

- `IpcCommands`, `IpcEvents` -- type-safe IPC channel definitions in `shared/ipc/channels.ts`
- `ipc.send`, `ipc.handle` -- typed wrappers in `shared/ipc/main.ts`
- `ThemeName`, `DebugState`, `DebugOverlays` -- shared types in `shared/ipc/types.ts`
- `ThemeName`, `Debug`, `DebugOverlays` -- shared types in `shared/ipc/types.ts`
- Preload bridge -- exposes IPC to renderer (see preload DOCS.md)
2 changes: 1 addition & 1 deletion apps/desktop/src/main/managers/AppLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class AppLifecycle {
this.menuManager.setTheme(theme);
});

ipc.handle(ipcMain, "debug:getState", () => this.menuManager.getDebugState());
ipc.handle(ipcMain, "debug:getState", () => this.menuManager.getDebug());

ipc.handle(ipcMain, "dialog:openFont", async () => {
const result = await dialog.showOpenDialog({
Expand Down
14 changes: 6 additions & 8 deletions apps/desktop/src/main/managers/MenuManager.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Menu, dialog, nativeTheme, app } from "electron";
import type { DocumentState } from "./DocumentState";
import type { WindowManager } from "./WindowManager";
import type { ThemeName, DebugOverlays, DebugState } from "../../shared/ipc/types";
import type { ThemeName, DebugOverlays, Debug } from "../../shared/ipc/types";
import type { IpcEvents } from "../../shared/ipc/channels";
import * as ipc from "../../shared/ipc/main";

export class MenuManager {
private documentState: DocumentState;
private windowManager: WindowManager;
private currentTheme: ThemeName = "light";
private debugState: DebugState = {
private debugState: Debug = {
reactScanEnabled: false,
debugPanelOpen: false,
overlays: {
Expand All @@ -29,7 +29,7 @@ export class MenuManager {
return this.currentTheme;
}

getDebugState(): DebugState {
getDebug(): Debug {
return { ...this.debugState };
}

Expand All @@ -42,7 +42,7 @@ export class MenuManager {
ipc.send(webContents, channel, ...args);
}

private setDebugState<K extends keyof DebugState>(key: K, value: DebugState[K]) {
private setDebug<K extends keyof Debug>(key: K, value: Debug[K]) {
this.debugState[key] = value;
switch (key) {
case "reactScanEnabled":
Expand Down Expand Up @@ -262,15 +262,13 @@ export class MenuManager {
label: "React Scan",
type: "checkbox" as const,
checked: this.debugState.reactScanEnabled,
click: () =>
this.setDebugState("reactScanEnabled", !this.debugState.reactScanEnabled),
click: () => this.setDebug("reactScanEnabled", !this.debugState.reactScanEnabled),
},
{
label: "Debug Panel",
type: "checkbox" as const,
checked: this.debugState.debugPanelOpen,
click: () =>
this.setDebugState("debugPanelOpen", !this.debugState.debugPanelOpen),
click: () => this.setDebug("debugPanelOpen", !this.debugState.debugPanelOpen),
},
{ type: "separator" as const },
{
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/preload/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const electronAPI: ElectronAPI = {
setDocumentDirty: invoke("document:setDirty"),
setDocumentFilePath: invoke("document:setFilePath"),
saveCompleted: invoke("document:saveCompleted"),
getDebugState: invoke("debug:getState"),
getDebug: invoke("debug:getState"),
pathsExist: invoke("fs:pathsExist"),

// Events
Expand Down
10 changes: 5 additions & 5 deletions apps/desktop/src/renderer/src/bridge/NativeBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import { constrainDrag } from "@shift/rules";
import { ValidateSnapshot } from "@shift/validation";
import { Glyphs } from "@shift/font";
import type { FontEngineAPI } from "@shared/bridge/FontEngineAPI";
import type { CompositeComponentsPayload } from "@shared/bridge/FontEngineAPI";
import type { CommandResponse, PasteResult, PointEdit } from "@/types/engine";
import type { CompositeComponents } from "@shared/bridge/FontEngineAPI";
import type { CommandResult, PasteResult, PointEdit } from "@/types/engine";
import { ContourContent } from "@/lib/clipboard";
import type { NodePositionUpdateList } from "@/types/positionUpdate";
import { Glyph, type GlyphChange } from "@/lib/model/Glyph";
Expand Down Expand Up @@ -141,10 +141,10 @@ export class NativeBridge {
return svg ? new Path2D(svg) : null;
}

getGlyphCompositeComponents(glyphName: string): CompositeComponentsPayload | null {
getGlyphCompositeComponents(glyphName: string): CompositeComponents | null {
const payload = this.#raw.getGlyphCompositeComponents(glyphName);
if (!payload) return null;
return JSON.parse(payload) as CompositeComponentsPayload;
return JSON.parse(payload) as CompositeComponents;
}

/** @knipclassignore — used by VariationPanel component */
Expand Down Expand Up @@ -184,7 +184,7 @@ export class NativeBridge {
return JSON.parse(this.#raw.getSnapshotData()) as GlyphSnapshot;
}

#execute(json: string): CommandResponse {
#execute(json: string): CommandResult {
const raw = JSON.parse(json);
if (!raw.success) {
throw new NativeOperationError(raw.error ?? "Unknown native error");
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/src/bridge/docs/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ The bridge separates JS-side reactivity from Rust-side persistence. Not every mu
`#dispatch` / `#dispatchVoid` are the standard Rust command path for structural mutations (addPoint, removePoints, closeContour, etc.):

1. Call NAPI method, receive JSON string
2. `#execute` parses JSON, checks `success`, extracts `CommandResponse` (snapshot + affectedPointIds)
2. `#execute` parses JSON, checks `success`, extracts `CommandResult` (snapshot + affectedPointIds)
3. `#syncFromResponse` applies the returned snapshot to the reactive `Glyph` via `Glyph.apply`

### NAPI position sync
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/src/context/DebugContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function DebugProvider({ children }: DebugProviderProps) {
useEffect(() => {
if (!isDev) return undefined;

window.electronAPI?.getDebugState().then((state) => {
window.electronAPI?.getDebug().then((state) => {
setReactScanEnabled(state.reactScanEnabled);
setDebugPanelOpen(state.debugPanelOpen);
if (state.overlays) {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/src/lib/clipboard/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface SystemClipboard {
}

/** Current in-memory clipboard state held by the clipboard service. */
export interface ClipboardState {
export interface Clipboard {
content: ClipboardContent | null;
bounds: Rect2D | null;
timestamp: number;
Expand Down
12 changes: 6 additions & 6 deletions apps/desktop/src/renderer/src/lib/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ import type {
} from "./snapping/types";
import { SnapManager } from "./managers/SnapManager";
import { TextRunController } from "@/lib/tools/text/TextRunController";
import { SnapPreferencesSchema, TextRunModulePayloadSchema } from "@shift/validation";
import type { TextRunModulePayload } from "@/persistence/types";
import { SnapPreferencesSchema, TextRunModuleSchema } from "@shift/validation";
import type { TextRunModule } from "@/persistence/types";

interface AppSettings {
snap: SnapPreferences;
Expand All @@ -102,7 +102,7 @@ const defaultAppSettings: AppSettings = {
},
};
import type { CompositeGlyph } from "@shift/types";
import type { ToolDescriptor, ToolShortcutEntry } from "@/types/tools";
import type { ToolManifest, ToolShortcutEntry } from "@/types/tools";
import type { ToolStateScope } from "@/types/editor";
import { EventEmitter } from "./lifecycle";
import { StateRegistry, type ShiftState, type ShiftStateOptions } from "@/lib/state/ShiftState";
Expand Down Expand Up @@ -294,13 +294,13 @@ export class Editor {
this.#textRunController = new TextRunController();
this.#textRunController.setFont(this.font);

const textRunPersistence = this.registerState<TextRunModulePayload>({
const textRunPersistence = this.registerState<TextRunModule>({
id: "text-run",
scope: "document",
initial: () => ({ runsByGlyph: {} }),
serialize: () => ({ runsByGlyph: this.#textRunController.exportRuns() }),
deserialize: (json) => {
const payload = TextRunModulePayloadSchema.parse(json);
const payload = TextRunModuleSchema.parse(json);
this.#textRunController.hydrateRuns(payload.runsByGlyph);
return payload;
},
Expand Down Expand Up @@ -386,7 +386,7 @@ export class Editor {
});
}

public registerTool(descriptor: ToolDescriptor): void {
public registerTool(descriptor: ToolManifest): void {
const { id, icon, tooltip, shortcut } = descriptor;
this.#toolMetadata.set(id, shortcut ? { icon, tooltip, shortcut } : { icon, tooltip });
this.toolManager.register(descriptor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import type { SnapPreferences } from "@/types/editor";
import type {
DragSnapSession,
DragSnapSessionConfig,
PointSnapResult,
PointSnap,
PointSnapStep,
RotateSnapSession,
RotateSnapResult,
RotateSnap,
RotateSnapStep,
SnapContext,
SnappableObject,
Expand Down Expand Up @@ -75,7 +75,7 @@ export class SnapManager {

return {
getAnchorPosition: () => anchorPosition,
snap: (cursorPoint, modifiers): PointSnapResult => {
snap: (cursorPoint, modifiers): PointSnap => {
const pointPosition = Vec2.add(anchorPosition, Vec2.sub(cursorPoint, config.dragStart));
const prefs = this.#getPreferences();

Expand All @@ -101,7 +101,7 @@ export class SnapManager {
const steps: RotateSnapStep[] = [createRotateAngleStep()];

return {
snap: (delta, modifiers): RotateSnapResult => {
snap: (delta, modifiers): RotateSnap => {
const prefs = this.#getPreferences();

return this.#runner.runRotatePipeline(steps, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { Vec2 } from "@shift/geo";
import type {
PointSnapStep,
PointSnapStepArgs,
PointSnapResult,
PointStepResult,
PointSnap,
PointStep,
RotateSnapStep,
RotateSnapStepArgs,
RotateSnapResult,
RotateSnap,
} from "./types";

/**
Expand All @@ -27,8 +27,8 @@ export class SnapPipelineRunner {
* to the original point is chosen.
* 3. **No match** — returns the input point unchanged with `source: null`.
*/
runPointPipeline(steps: readonly PointSnapStep[], args: PointSnapStepArgs): PointSnapResult {
const candidates: PointStepResult[] = [];
runPointPipeline(steps: readonly PointSnapStep[], args: PointSnapStepArgs): PointSnap {
const candidates: PointStep[] = [];

for (const step of steps) {
const result = step.apply(args);
Expand Down Expand Up @@ -60,7 +60,7 @@ export class SnapPipelineRunner {
* Uses **first-match** semantics: the first step that returns a non-null result
* wins. If no step matches, the raw delta passes through with `source: null`.
*/
runRotatePipeline(steps: readonly RotateSnapStep[], args: RotateSnapStepArgs): RotateSnapResult {
runRotatePipeline(steps: readonly RotateSnapStep[], args: RotateSnapStepArgs): RotateSnap {
for (const step of steps) {
const result = step.apply(args);
if (result) {
Expand Down
20 changes: 10 additions & 10 deletions apps/desktop/src/renderer/src/lib/editor/snapping/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,19 @@ export interface PointSnapStepArgs {
}

/** Output of a single point snap step: the corrected position, which source matched, and optional visual indicator. */
export interface PointStepResult {
export interface PointStep {
snappedPoint: Point2D;
source: "pointToPoint" | "metrics" | "angle";
indicator: SnapIndicator | null;
}

/**
* A single stage in the point snap pipeline. Each step inspects the input and
* either returns a {@link PointStepResult} (snap hit) or `null` (no match).
* either returns a {@link PointStep} (snap hit) or `null` (no match).
*/
export interface PointSnapStep {
id: string;
apply(args: PointSnapStepArgs): PointStepResult | null;
apply(args: PointSnapStepArgs): PointStep | null;
}

/** Input bundle passed to each {@link RotateSnapStep}. Contains the raw rotation delta (radians) and modifier state. */
Expand All @@ -97,30 +97,30 @@ export interface RotateSnapStepArgs {
}

/** Output of a single rotate snap step: the quantized delta and its source. */
export interface RotateStepResult {
export interface RotateStep {
snappedDelta: number;
source: "angle";
indicator: SnapIndicator | null;
}

/**
* A single stage in the rotate snap pipeline. Returns a {@link RotateStepResult}
* A single stage in the rotate snap pipeline. Returns a {@link RotateStep}
* when the rotation delta should be quantized, or `null` to pass through.
*/
export interface RotateSnapStep {
id: string;
apply(args: RotateSnapStepArgs): RotateStepResult | null;
apply(args: RotateSnapStepArgs): RotateStep | null;
}

/** Final resolved result of a point snap pipeline run. If no step matched, `source` is `null` and `point` is unchanged. */
export interface PointSnapResult {
export interface PointSnap {
point: Point2D;
indicator: SnapIndicator | null;
source: "pointToPoint" | "metrics" | "angle" | null;
}

/** Final resolved result of a rotate snap pipeline run. If no step matched, `source` is `null` and `delta` is unchanged. */
export interface RotateSnapResult {
export interface RotateSnap {
delta: number;
source: "angle" | null;
}
Expand All @@ -142,7 +142,7 @@ export interface DragSnapSessionConfig {
*/
export interface DragSnapSession {
getAnchorPosition(): Point2D;
snap(point: Point2D, modifiers: { shiftKey: boolean }): PointSnapResult;
snap(point: Point2D, modifiers: { shiftKey: boolean }): PointSnap;
clear(): void;
}

Expand All @@ -151,6 +151,6 @@ export interface DragSnapSession {
* call `snap()` on each rotation event and `clear()` when rotation ends.
*/
export interface RotateSnapSession {
snap(delta: number, modifiers: { shiftKey: boolean }): RotateSnapResult;
snap(delta: number, modifiers: { shiftKey: boolean }): RotateSnap;
clear(): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Shape } from "../shape/Shape";
import { Pen } from "../pen/Pen";
import { Select } from "../select/Select";
import { asPointId } from "@shift/types";
import type { HandleData } from "../pen/types";
import type { Handles } from "../pen/types";

const p = { x: 0, y: 0 };
const coordsP = makeTestCoordinates(p);
Expand Down Expand Up @@ -188,7 +188,7 @@ describe("State diagram compliance", () => {
const draggingState = {
type: "dragging" as const,
anchor: anchor.anchor,
handles: {} as HandleData,
handles: {} as Handles,
mousePos: p,
};

Expand Down
Loading
Loading