Migrate frontend from Svelte 5 to React 18
After hours of fighting Svelte 5's prop-reactivity through the
recursive Pane → SplitNode → LeafPane chain (props captured at
mount, never updated; context+getter pattern crashed; DOM-direct
workarounds created zombie-split click-intercept bugs), we
checkpointed the Svelte version (branch svelte-archive at e9015b2,
tarball at D:\archives\tiletopia-svelte-2026-05-22.tar.gz) and
rewrote the frontend in React.
Kept verbatim:
- All of src-tauri/ (Rust backend, Tauri config, icons)
- scripts/ (make-icon.py, release.sh)
- README.md, CLAUDE.md, memory.md
- src/lib/layout/tree.ts (pure TS — 43 tests still pass)
- src/ipc.ts (Tauri command wrappers)
Rewrote in React:
- src/App.tsx (top-level state via useState, OrchestrationProvider
for descendants via React.Context)
- src/lib/layout/orchestration.tsx (React Context API for shared
state — known-reliable reactivity, no Svelte 5 wall)
- src/lib/layout/Pane.tsx (recursive dispatcher)
- src/lib/layout/SplitNode.tsx (draggable gutter, local ratio state)
- src/lib/layout/LeafPane.tsx (toolbar + XtermPane)
- src/components/XtermPane.tsx (xterm.js wrapper, refs for callbacks)
- src/components/Notifications.tsx, Palette.tsx
Build: Vite + @vitejs/plugin-react. TypeScript strict. Same Tauri 2
config. Verified: pnpm check (clean), pnpm test (43/43 pass).
Not yet verified: pnpm tauri dev — that requires the Windows host.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e9015b2790
commit
774b8633dc
32 changed files with 2087 additions and 1825 deletions
58
src/lib/layout/orchestration.tsx
Normal file
58
src/lib/layout/orchestration.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { createContext, useContext, type ReactNode } from "react";
|
||||
import type { Orientation, NodeId } from "./tree";
|
||||
import type { PaneId } from "../../ipc";
|
||||
|
||||
/**
|
||||
* Orchestration context — every piece of shared state and every operation
|
||||
* that a Pane / SplitNode / LeafPane might call. Lives in React context so
|
||||
* descendants can `useOrchestration()` without prop drilling.
|
||||
*
|
||||
* activeLeafId comes in as a plain value (re-derived by App's useState).
|
||||
* React's context is reactive: when the App-level Provider updates the
|
||||
* value, ALL consumers re-render. No Svelte-style props-don't-propagate
|
||||
* trap here.
|
||||
*/
|
||||
export interface Orchestration {
|
||||
// Read-only state
|
||||
activeLeafId: NodeId | null;
|
||||
distros: string[];
|
||||
|
||||
// Tree mutations
|
||||
split: (leafId: NodeId, orientation: Orientation) => void;
|
||||
close: (leafId: NodeId) => void;
|
||||
setDistro: (leafId: NodeId, distro: string) => void;
|
||||
setLabel: (leafId: NodeId, label: string | undefined) => void;
|
||||
toggleBroadcast: (leafId: NodeId) => void;
|
||||
|
||||
// Per-pane orchestration
|
||||
setActive: (leafId: NodeId) => void;
|
||||
registerPaneId: (leafId: NodeId, paneId: PaneId | null) => void;
|
||||
broadcastFrom: (originLeafId: NodeId, dataB64: string) => void;
|
||||
notify: (message: string) => void;
|
||||
}
|
||||
|
||||
const OrchestrationContext = createContext<Orchestration | null>(null);
|
||||
|
||||
export function OrchestrationProvider({
|
||||
value,
|
||||
children,
|
||||
}: {
|
||||
value: Orchestration;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<OrchestrationContext.Provider value={value}>
|
||||
{children}
|
||||
</OrchestrationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useOrchestration(): Orchestration {
|
||||
const orch = useContext(OrchestrationContext);
|
||||
if (!orch) {
|
||||
throw new Error(
|
||||
"useOrchestration() must be called inside <OrchestrationProvider>",
|
||||
);
|
||||
}
|
||||
return orch;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue