diff --git a/Client/tauri-client/package-lock.json b/Client/tauri-client/package-lock.json
index cbd8c8c7..70f45976 100644
--- a/Client/tauri-client/package-lock.json
+++ b/Client/tauri-client/package-lock.json
@@ -20,13 +20,11 @@
"@tauri-apps/plugin-store": "^2",
"@tauri-apps/plugin-updater": "^2.10.0",
"livekit-client": "^2.18.0",
- "solid-js": "^1.9.3",
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
"@playwright/test": "^1",
- "@solidjs/testing-library": "^0.8.10",
"@stryker-mutator/core": "^9.6.0",
"@stryker-mutator/typescript-checker": "^9.6.0",
"@stryker-mutator/vitest-runner": "^9.6.0",
@@ -41,7 +39,6 @@
"typescript": "^5.7",
"typescript-eslint": "^8.58.0",
"vite": "^6",
- "vite-plugin-solid": "^2.11.0",
"vitest": "^3"
}
},
@@ -3461,28 +3458,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@solidjs/testing-library": {
- "version": "0.8.10",
- "resolved": "https://registry.npmjs.org/@solidjs/testing-library/-/testing-library-0.8.10.tgz",
- "integrity": "sha512-qdeuIerwyq7oQTIrrKvV0aL9aFeuwTd86VYD3afdq5HYEwoox1OBTJy4y8A3TFZr8oAR0nujYgCzY/8wgHGfeQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@testing-library/dom": "^10.4.0"
- },
- "engines": {
- "node": ">= 14"
- },
- "peerDependencies": {
- "@solidjs/router": ">=0.9.0",
- "solid-js": ">=1.0.0"
- },
- "peerDependenciesMeta": {
- "@solidjs/router": {
- "optional": true
- }
- }
- },
"node_modules/@stryker-mutator/api": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-9.6.0.tgz",
@@ -4013,51 +3988,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "node_modules/@types/babel__generator": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.2"
- }
- },
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -4640,68 +4570,6 @@
"js-tokens": "^10.0.0"
}
},
- "node_modules/babel-plugin-jsx-dom-expressions": {
- "version": "0.40.6",
- "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.6.tgz",
- "integrity": "sha512-v3P1MW46Lm7VMpAkq0QfyzLWWkC8fh+0aE5Km4msIgDx5kjenHU0pF2s+4/NH8CQn/kla6+Hvws+2AF7bfV5qQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "7.18.6",
- "@babel/plugin-syntax-jsx": "^7.18.6",
- "@babel/types": "^7.20.7",
- "html-entities": "2.3.3",
- "parse5": "^7.1.2"
- },
- "peerDependencies": {
- "@babel/core": "^7.20.12"
- }
- },
- "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
- "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.18.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-jsx-dom-expressions/node_modules/parse5": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
- "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "entities": "^6.0.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/babel-preset-solid": {
- "version": "1.9.12",
- "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.12.tgz",
- "integrity": "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "babel-plugin-jsx-dom-expressions": "^0.40.6"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0",
- "solid-js": "^1.9.12"
- },
- "peerDependenciesMeta": {
- "solid-js": {
- "optional": true
- }
- }
- },
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -5017,12 +4885,6 @@
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
- "node_modules/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "license": "MIT"
- },
"node_modules/data-urls": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
@@ -6002,13 +5864,6 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
- "node_modules/html-entities": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz",
- "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -6176,19 +6031,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-what": {
- "version": "4.1.16",
- "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
- "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.13"
- },
- "funding": {
- "url": "https://github.com/sponsors/mesqueeb"
- }
- },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -6647,22 +6489,6 @@
"dev": true,
"license": "CC0-1.0"
},
- "node_modules/merge-anything": {
- "version": "5.1.7",
- "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz",
- "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-what": "^4.1.8"
- },
- "engines": {
- "node": ">=12.13"
- },
- "funding": {
- "url": "https://github.com/sponsors/mesqueeb"
- }
- },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -7560,27 +7386,6 @@
"node": ">=10"
}
},
- "node_modules/seroval": {
- "version": "1.5.2",
- "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.2.tgz",
- "integrity": "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/seroval-plugins": {
- "version": "1.5.2",
- "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.2.tgz",
- "integrity": "sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "seroval": "^1.0"
- }
- },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -7728,32 +7533,6 @@
"url": "https://github.com/sponsors/cyyynthia"
}
},
- "node_modules/solid-js": {
- "version": "1.9.12",
- "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.12.tgz",
- "integrity": "sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==",
- "license": "MIT",
- "dependencies": {
- "csstype": "^3.1.0",
- "seroval": "~1.5.0",
- "seroval-plugins": "~1.5.0"
- }
- },
- "node_modules/solid-refresh": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.6.3.tgz",
- "integrity": "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/generator": "^7.23.6",
- "@babel/helper-module-imports": "^7.22.15",
- "@babel/types": "^7.23.6"
- },
- "peerDependencies": {
- "solid-js": "^1.3"
- }
- },
"node_modules/source-map": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
@@ -8408,51 +8187,6 @@
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/vite-plugin-solid": {
- "version": "2.11.12",
- "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.11.12.tgz",
- "integrity": "sha512-FgjPcx2OwX9h6f28jli7A4bG7PP3te8uyakE5iqsmpq3Jqi1TWLgSroC9N6cMfGRU2zXsl4Q6ISvTr2VL0QHpA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.23.3",
- "@types/babel__core": "^7.20.4",
- "babel-preset-solid": "^1.8.4",
- "merge-anything": "^5.1.7",
- "solid-refresh": "^0.6.3",
- "vitefu": "^1.0.4"
- },
- "peerDependencies": {
- "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*",
- "solid-js": "^1.7.2",
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "@testing-library/jest-dom": {
- "optional": true
- }
- }
- },
- "node_modules/vitefu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz",
- "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==",
- "dev": true,
- "license": "MIT",
- "workspaces": [
- "tests/deps/*",
- "tests/projects/*",
- "tests/projects/workspace/packages/*"
- ],
- "peerDependencies": {
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "vite": {
- "optional": true
- }
- }
- },
"node_modules/vitest": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz",
diff --git a/Client/tauri-client/package.json b/Client/tauri-client/package.json
index 20e0e5b4..d92acbef 100644
--- a/Client/tauri-client/package.json
+++ b/Client/tauri-client/package.json
@@ -32,7 +32,6 @@
"devDependencies": {
"@eslint/js": "^9.39.4",
"@playwright/test": "^1",
- "@solidjs/testing-library": "^0.8.10",
"@stryker-mutator/core": "^9.6.0",
"@stryker-mutator/typescript-checker": "^9.6.0",
"@stryker-mutator/vitest-runner": "^9.6.0",
@@ -47,7 +46,6 @@
"typescript": "^5.7",
"typescript-eslint": "^8.58.0",
"vite": "^6",
- "vite-plugin-solid": "^2.11.0",
"vitest": "^3"
},
"prettier": {
@@ -72,7 +70,6 @@
"@tauri-apps/plugin-store": "^2",
"@tauri-apps/plugin-updater": "^2.10.0",
"livekit-client": "^2.18.0",
- "solid-js": "^1.9.3",
"zod": "^4.3.6"
}
}
diff --git a/Client/tauri-client/src/components/solid/Badge.test.tsx b/Client/tauri-client/src/components/solid/Badge.test.tsx
deleted file mode 100644
index cb4e5397..00000000
--- a/Client/tauri-client/src/components/solid/Badge.test.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Phase B Step 6 — Solid pipeline smoke test.
- *
- * Verifies that the Vite + Solid + Vitest configuration actually compiles
- * and renders a component end-to-end. This test only exists to prove the
- * pipeline; the test scope expands as more components migrate.
- */
-
-import { describe, it, expect } from "vitest";
-import { render } from "@solidjs/testing-library";
-import { Badge } from "./Badge";
-
-describe("Badge (solid)", () => {
- it("renders the label", () => {
- const { getByText } = render(() => );
- expect(getByText("online")).toBeTruthy();
- });
-
- it("invokes onClick when activated", () => {
- let clicked = 0;
- const { getByText } = render(() => (
- clicked++} />
- ));
- getByText("press me").click();
- expect(clicked).toBe(1);
- });
-});
diff --git a/Client/tauri-client/src/components/solid/Badge.tsx b/Client/tauri-client/src/components/solid/Badge.tsx
deleted file mode 100644
index 32570aef..00000000
--- a/Client/tauri-client/src/components/solid/Badge.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Phase B Step 6 — first Solid.js leaf component.
- *
- * Trivial proof-of-concept badge used by other Solid components and the
- * mount helper. Self-contained: no store subscriptions, no async work, just
- * a presentational element. Use it as the canonical example when migrating
- * vanilla badge/pill components in the rest of the tree.
- */
-
-import type { JSX } from "solid-js";
-
-export interface BadgeProps {
- label: string;
- /** Visual variant; defaults to "neutral". */
- variant?: "neutral" | "online" | "idle" | "dnd" | "offline";
- /** Optional click handler. When set the badge gains role="button". */
- onClick?: () => void;
-}
-
-const variantClass: Record, string> = {
- neutral: "badge",
- online: "badge badge--online",
- idle: "badge badge--idle",
- dnd: "badge badge--dnd",
- offline: "badge badge--offline",
-};
-
-export function Badge(props: BadgeProps): JSX.Element {
- const cls = () => variantClass[props.variant ?? "neutral"];
- return (
- {
- if (props.onClick && (e.key === "Enter" || e.key === " ")) {
- e.preventDefault();
- props.onClick();
- }
- }}
- >
- {props.label}
-
- );
-}
diff --git a/Client/tauri-client/src/components/solid/ChannelListItem.tsx b/Client/tauri-client/src/components/solid/ChannelListItem.tsx
deleted file mode 100644
index 60bf4b4c..00000000
--- a/Client/tauri-client/src/components/solid/ChannelListItem.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Phase B Step 6 — second Solid.js leaf component.
- *
- * Renders a single row in the channel list. Reads from the existing
- * `channelsStore` via the Solid adapter so it stays in sync with whatever
- * the vanilla dispatcher writes into the store.
- *
- * This is the canonical example for migrating list-item components: pure
- * presentation, fed by an accessor, with a click handler delegated up to the
- * parent so the component knows nothing about the dispatcher.
- */
-
-import type { JSX } from "solid-js";
-import { Show } from "solid-js";
-import { fromStoreSlice } from "@lib/solidAdapter";
-import { channelsStore, type Channel, type ChannelsState } from "@stores/channels.store";
-import { Badge } from "./Badge";
-
-export interface ChannelListItemProps {
- channelId: number;
- onSelect: (id: number) => void;
-}
-
-export function ChannelListItem(props: ChannelListItemProps): JSX.Element {
- // Subscribe to just this row's channel object so unrelated changes don't
- // re-render. Falsy → row is hidden until the channel arrives.
- const channel = fromStoreSlice(
- channelsStore,
- (s) => s.channels.get(props.channelId),
- );
- const isActive = fromStoreSlice(
- channelsStore,
- (s) => s.activeChannelId === props.channelId,
- );
-
- return (
-
- {(ch) => (
- props.onSelect(props.channelId)}
- >
- #
- {ch().name}
- 0}>
-
-
-
- )}
-
- );
-}
diff --git a/Client/tauri-client/src/components/solid/PluginContainer.tsx b/Client/tauri-client/src/components/solid/PluginContainer.tsx
deleted file mode 100644
index 0901d433..00000000
--- a/Client/tauri-client/src/components/solid/PluginContainer.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Phase C Step 9 — Solid component that hosts a plugin tab.
- *
- * Mounts the plugin's iframe via `pluginBridge.mount(...)` and tears it down
- * when the component is disposed. The container itself is intentionally tiny:
- * the bridge owns the iframe lifecycle and the postMessage protocol.
- */
-
-import { onMount, onCleanup, type JSX } from "solid-js";
-import { pluginBridge, type PluginTabBinding } from "@lib/pluginBridge";
-
-export interface PluginContainerProps {
- binding: PluginTabBinding;
-}
-
-export function PluginContainer(props: PluginContainerProps): JSX.Element {
- // oxlint-disable-next-line no-unassigned-vars -- Solid ref assigned by JSX ref={host}
- let host!: HTMLDivElement;
- let dispose: (() => void) | undefined;
-
- onMount(() => {
- dispose = pluginBridge.mount(props.binding, host);
- });
- onCleanup(() => {
- dispose?.();
- });
-
- return ;
-}
diff --git a/Client/tauri-client/src/components/solid/README.md b/Client/tauri-client/src/components/solid/README.md
deleted file mode 100644
index 4bf52631..00000000
--- a/Client/tauri-client/src/components/solid/README.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# Solid.js components
-
-Phase B Step 6 lives here. This directory holds the incremental Solid.js
-migration of the OwnCord client. Vanilla TypeScript components and Solid
-components coexist throughout the migration; new UI work goes in this
-directory, and existing leaf components are ported one PR at a time.
-
-## Migration recipe
-
-1. **Pick a leaf component.** Start with components that have no children of
- their own and read at most one or two stores. Avoid container components
- until every leaf inside them is Solid-native.
-2. **Read state via the adapter.** Import `fromStore` or `fromStoreSlice` from
- `@lib/solidAdapter` and pass the existing custom store. Do **not** rewrite
- the store — Solid components and vanilla components share the same source
- of truth.
-3. **Mount via `solidMount`.** Containers that aren't yet Solid-native should
- call `mountSolid(component, parentEl)` from `@lib/solidMount`. The returned
- handle has the same `{ destroy }` shape that the rest of the codebase uses.
-4. **Test the pipeline.** New components get a `*.test.tsx` next to them
- using `@solidjs/testing-library`. The tests run under the existing Vitest
- configuration without any extra setup. Do **not** call `cleanup()` manually
- in test files — `tests/setup-solid.ts` registers `afterEach(cleanup)` globally
- via Vitest's `setupFiles` (added in T-500).
-5. **Delete vanilla DOM code.** Once a component is fully migrated, remove
- the old factory function and update its callers to import from
- `@components/solid/...`.
-
-## Allowed reactivity
-
-- `createSignal`, `createMemo`, `createEffect`, `createResource`
-- `Show`, `For`, `Switch`/`Match`, `Index`
-- `onMount`, `onCleanup`
-
-## Forbidden patterns
-
-- Direct DOM manipulation inside Solid components — use Solid's bindings or a
- ref. The point of the migration is to delete manual DOM lifecycle code.
-- Re-implementing existing stores in Solid's `createStore`. Wrap the existing
- custom store via `fromStore` instead.
-- Touching framework-agnostic code (`lib/ws.ts`, `lib/dispatcher.ts`,
- `lib/livekitSession.ts`, `lib/api.ts`). These never need to know about Solid.
-- Using `innerHTML`, `insertAdjacentHTML`, or `dangerouslySetInnerHTML` in any
- Solid component. JSX interpolation (`{value}`) auto-escapes user content and
- is the correct approach. Components rendering user-supplied text (e.g. message
- attachments, toast messages) must never bypass this escaping.
-- Placing `.tsx` files outside `src/components/solid/`. The `vite-plugin-solid`
- transform is scoped to this directory in both `vite.config.ts` and
- `vitest.config.ts`. A `.tsx` file elsewhere will fail with cryptic JSX parse
- errors at build time.
-
-## Existing components
-
-- `Badge.tsx` — presentational badge / pill (no store dependency)
-- `ChannelListItem.tsx` — single channel row (subscribes to channels.store)
-
-The plugin client bridge introduced by Phase C also adds a `PluginContainer`
-component to this directory; see `Server/plugin/host_ui.go` for the host
-side of that contract.
diff --git a/Client/tauri-client/src/lib/solidAdapter.ts b/Client/tauri-client/src/lib/solidAdapter.ts
deleted file mode 100644
index e99bc1ac..00000000
--- a/Client/tauri-client/src/lib/solidAdapter.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Phase B Step 6 — Solid.js adapter.
- *
- * Bridges the existing custom reactive `Store` (lib/store.ts) into Solid's
- * signal model so migrated components can read from existing stores without
- * touching them. Vanilla and Solid components coexist throughout the
- * migration: a vanilla component can update a store, and any Solid component
- * subscribed via this adapter sees the new value through its accessor.
- *
- * Usage from a Solid component:
- *
- * import { fromStore } from "@lib/solidAdapter";
- * import { authStore } from "@stores/auth";
- *
- * const auth = fromStore(authStore);
- * return {auth().username}
;
- *
- * The accessor returned by fromStore is a Solid signal getter, so it triggers
- * fine-grained reactivity in any computation, JSX expression, or `createMemo`.
- */
-
-import { createSignal, onCleanup, type Accessor } from "solid-js";
-import type { Store } from "./store";
-
-/**
- * Wrap a custom Store as a Solid signal accessor. The signal updates whenever
- * the underlying store fires, and the subscription is torn down when the
- * Solid owner is disposed (so leaf components don't leak listeners).
- */
-export function fromStore(store: Store): Accessor {
- const [value, setValue] = createSignal(store.getState(), { equals: false });
- const unsub = store.subscribe((next) => setValue(() => next));
- onCleanup(unsub);
- return value;
-}
-
-/**
- * Wrap a derived slice of a store. Equivalent to fromStore(store).map(selector)
- * but uses the store's native subscribeSelector so changes are gated by the
- * existing equality comparator.
- */
-export function fromStoreSlice(
- store: Store,
- selector: (state: T) => S,
- isEqual?: (a: S, b: S) => boolean,
-): Accessor {
- const initial = selector(store.getState());
- const [value, setValue] = createSignal(initial, { equals: false });
- const unsub = store.subscribeSelector(selector, (next) => setValue(() => next), isEqual);
- onCleanup(unsub);
- return value;
-}
diff --git a/Client/tauri-client/src/lib/solidMount.ts b/Client/tauri-client/src/lib/solidMount.ts
deleted file mode 100644
index a4c1ad79..00000000
--- a/Client/tauri-client/src/lib/solidMount.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Phase B Step 6 — Solid.js mount helper.
- *
- * Wraps Solid's `render(...)` so a Solid component conforms to the
- * `{ mount, destroy }` factory contract used everywhere else in the vanilla
- * codebase. Existing container components can host a Solid leaf without
- * being aware of Solid at all:
- *
- * import { mountSolid } from "@lib/solidMount";
- * import { Badge } from "@components/solid/Badge";
- *
- * const handle = mountSolid(() => Badge({ label: "online" }), parentEl);
- * // …later
- * handle.destroy();
- */
-
-import { render } from "solid-js/web";
-import type { JSX } from "solid-js";
-
-export interface SolidMount {
- /** The DOM element the Solid root is rendered into. */
- el: HTMLElement;
- /** Tear the Solid root down and remove it from the DOM. */
- destroy(): void;
-}
-
-export function mountSolid(component: () => JSX.Element, parent: HTMLElement): SolidMount {
- const host = document.createElement("div");
- host.dataset.solidRoot = "true";
- parent.appendChild(host);
- const dispose = render(component, host);
- return {
- el: host,
- destroy() {
- dispose();
- host.remove();
- },
- };
-}
diff --git a/Client/tauri-client/tests/setup-solid.test.tsx b/Client/tauri-client/tests/setup-solid.test.tsx
deleted file mode 100644
index ea365fb9..00000000
--- a/Client/tauri-client/tests/setup-solid.test.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Phase B Step 6 — mountSolid adapter lifecycle smoke tests (T-500).
- *
- * Verifies that the mountSolid adapter in @lib/solidMount correctly inserts a
- * Solid reactive root into the DOM and that destroy() disposes the root and
- * removes the host element, preventing memory leaks across test suites.
- *
- * These tests complement Badge.test.tsx (which validates the Solid rendering
- * pipeline end-to-end) by proving the *adapter* contract used by vanilla-DOM
- * container components.
- */
-import { describe, it, expect } from "vitest";
-import { mountSolid } from "@lib/solidMount";
-
-describe("mountSolid adapter", () => {
- it("appends a data-solid-root host element to the parent on mount", () => {
- const parent = document.createElement("div");
- document.body.appendChild(parent);
-
- const handle = mountSolid(() => test, parent);
-
- expect(parent.querySelector("[data-solid-root]")).not.toBeNull();
- expect(handle.el.dataset.solidRoot).toBe("true");
- expect(handle.el.parentElement).toBe(parent);
-
- handle.destroy();
- parent.remove();
- });
-
- it("removes the host element from the DOM after destroy()", () => {
- const parent = document.createElement("div");
- document.body.appendChild(parent);
-
- const handle = mountSolid(() => cleanup-check, parent);
- expect(parent.children).toHaveLength(1);
-
- handle.destroy();
-
- expect(parent.querySelector("[data-solid-root]")).toBeNull();
- expect(parent.children).toHaveLength(0);
-
- parent.remove();
- });
-
- it("supports multiple independent mounts under the same parent", () => {
- const parent = document.createElement("div");
- document.body.appendChild(parent);
-
- const a = mountSolid(() => a, parent);
- const b = mountSolid(() => b, parent);
-
- expect(parent.querySelectorAll("[data-solid-root]")).toHaveLength(2);
-
- a.destroy();
- expect(parent.querySelectorAll("[data-solid-root]")).toHaveLength(1);
-
- b.destroy();
- expect(parent.querySelectorAll("[data-solid-root]")).toHaveLength(0);
-
- parent.remove();
- });
-
- it("exposes the host element via handle.el", () => {
- const parent = document.createElement("div");
- document.body.appendChild(parent);
-
- const handle = mountSolid(() => el-check, parent);
-
- expect(handle.el).toBeInstanceOf(HTMLElement);
- expect(handle.el).toBe(parent.firstElementChild);
-
- handle.destroy();
- parent.remove();
- });
-});
diff --git a/Client/tauri-client/tests/setup-solid.ts b/Client/tauri-client/tests/setup-solid.ts
deleted file mode 100644
index e78791f5..00000000
--- a/Client/tauri-client/tests/setup-solid.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Vitest global setup for Solid.js component tests (T-500).
- *
- * Loaded via `test.setupFiles` in vitest.config.ts so every test suite
- * automatically gets Solid's afterEach cleanup without having to import
- * or call it manually.
- *
- * Add future Solid testing helpers here (custom matchers, query extensions,
- * aria-query configuration, etc.). Do NOT import application code here —
- * this file executes once before every test suite, including non-Solid suites.
- *
- * Security note: Solid JSX auto-escapes interpolated values ({expr}), so
- * user-controlled strings passed through JSX are safe. Never use innerHTML,
- * insertAdjacentHTML, or dangerouslySetInnerHTML in Solid components.
- */
-import { cleanup } from "@solidjs/testing-library";
-import { afterEach } from "vitest";
-
-// Register cleanup after every test so Solid reactive roots are disposed
-// and host DOM nodes are removed. Without this, roots accumulate across tests
-// and can cause state leakage between test cases.
-afterEach(cleanup);
diff --git a/Client/tauri-client/tsconfig.json b/Client/tauri-client/tsconfig.json
index 45b5dc20..85aa443d 100644
--- a/Client/tauri-client/tsconfig.json
+++ b/Client/tauri-client/tsconfig.json
@@ -11,36 +11,15 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
- "jsx": "preserve",
- "jsxImportSource": "solid-js",
- "lib": [
- "ES2023",
- "DOM",
- "DOM.Iterable"
- ],
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
"paths": {
- "@lib/*": [
- "./src/lib/*"
- ],
- "@stores/*": [
- "./src/stores/*"
- ],
- "@components/*": [
- "./src/components/*"
- ],
- "@pages/*": [
- "./src/pages/*"
- ],
- "@styles/*": [
- "./src/styles/*"
- ]
+ "@lib/*": ["./src/lib/*"],
+ "@stores/*": ["./src/stores/*"],
+ "@components/*": ["./src/components/*"],
+ "@pages/*": ["./src/pages/*"],
+ "@styles/*": ["./src/styles/*"]
}
},
- "include": [
- "src",
- "tests"
- ],
- "exclude": [
- "tests/e2e"
- ]
+ "include": ["src", "tests"],
+ "exclude": ["tests/e2e"]
}
diff --git a/Client/tauri-client/vite.config.ts b/Client/tauri-client/vite.config.ts
index 8f5a5449..7e65c900 100644
--- a/Client/tauri-client/vite.config.ts
+++ b/Client/tauri-client/vite.config.ts
@@ -1,10 +1,5 @@
import { defineConfig, type Plugin } from "vite";
import { resolve } from "path";
-// Phase B Step 6 — Solid.js incremental migration. The plugin compiles
-// JSX/TSX files anywhere under src/components/solid/ to direct DOM ops, while
-// the rest of the vanilla codebase keeps building unchanged. The plugin is a
-// no-op for files that don't contain Solid syntax.
-import solidPlugin from "vite-plugin-solid";
const host = process.env.TAURI_DEV_HOST;
@@ -19,13 +14,7 @@ function stripCrossOrigin(): Plugin {
}
export default defineConfig({
- plugins: [
- // Solid first so its JSX transform runs before any other transforms.
- solidPlugin({
- include: ["src/components/solid/**/*.{ts,tsx,js,jsx}"],
- }),
- stripCrossOrigin(),
- ],
+ plugins: [stripCrossOrigin()],
build: {
modulePreload: { polyfill: false },
cssCodeSplit: false,
@@ -44,8 +33,6 @@ export default defineConfig({
port: 1420,
strictPort: true,
host: host || false,
- hmr: host
- ? { protocol: "ws", host, port: 1421 }
- : undefined,
+ hmr: host ? { protocol: "ws", host, port: 1421 } : undefined,
},
});
diff --git a/Client/tauri-client/vitest.config.ts b/Client/tauri-client/vitest.config.ts
index fbc987c4..cfa111de 100644
--- a/Client/tauri-client/vitest.config.ts
+++ b/Client/tauri-client/vitest.config.ts
@@ -1,19 +1,7 @@
import { defineConfig } from "vitest/config";
import { resolve } from "path";
-import solidPlugin from "vite-plugin-solid";
export default defineConfig({
- // The Solid plugin must be applied here in addition to vite.config.ts so
- // Vitest can transform `.tsx` test files under src/components/solid/.
- // Without it, JSX in component tests is parsed as TypeScript and fails on
- // the angle brackets.
- plugins: [
- solidPlugin({
- // Phase B Step 6: cover both the component directory and test files
- // under tests/ that use JSX (e.g. setup-solid.test.tsx, T-500).
- include: ["src/components/solid/**/*.{ts,tsx,js,jsx}", "tests/**/*.tsx"],
- }),
- ],
resolve: {
alias: {
"@lib": resolve(__dirname, "src/lib"),
@@ -25,15 +13,9 @@ export default defineConfig({
},
test: {
environment: "jsdom",
- // Both the legacy `tests/**/*.test.ts` suite and component-local
- // `src/**/*.test.{ts,tsx}` files are picked up. The latter is required
- // for Phase B Step 6 Solid components, whose tests live alongside the
- // component file (see src/components/solid/README.md).
- // T-500: also pick up .tsx test files under tests/ (e.g. setup-solid.test.tsx).
- include: ["tests/**/*.test.ts", "tests/**/*.test.tsx", "src/**/*.test.ts", "src/**/*.test.tsx"],
- // T-500: global Solid.js test setup — registers afterEach(cleanup) so
- // individual *.test.tsx files do not need to call cleanup() manually.
- setupFiles: ["./tests/setup-solid.ts"],
+ // Both the `tests/**/*.test.ts` suite and component-local
+ // `src/**/*.test.ts` files are picked up.
+ include: ["tests/**/*.test.ts", "src/**/*.test.ts"],
coverage: {
provider: "v8",
include: ["src/**/*.ts"],
diff --git a/README.md b/README.md
index 61cf97f5..1e169bf3 100644
--- a/README.md
+++ b/README.md
@@ -96,7 +96,7 @@ The client uses TOFU (Trust On First Use) for self-signed certificates: it promp
- TOTP 2FA support and API rate limiting
- Desktop client auto-update with signature verification
-See deeper feature and architecture docs in [docs/client-architecture.md](docs/client-architecture.md) and [docs/protocol.md](docs/protocol.md).
+See deeper feature and architecture docs in [docs/architecture/](docs/architecture/README.md) and [docs/protocol.md](docs/protocol.md).
## Architecture
@@ -212,7 +212,7 @@ When rotating the server updater key, update [Server/updater/server_update_publi
- [docs/api.md](docs/api.md)
- [docs/protocol.md](docs/protocol.md)
- [docs/schema.md](docs/schema.md)
-- [docs/client-architecture.md](docs/client-architecture.md)
+- [docs/architecture/client.md](docs/architecture/client.md) — client architecture (replaces client-architecture.md)
- [docs/contributing.md](docs/contributing.md)
- [docs/security.md](docs/security.md)
diff --git a/docs/audit-2026-07-19.md b/docs/audit-2026-07-19.md
index 0e02ebc1..5b3285c3 100644
--- a/docs/audit-2026-07-19.md
+++ b/docs/audit-2026-07-19.md
@@ -25,7 +25,7 @@ accepted-risk note before the beta gate. MEDIUMs are folded into the backlog
| A-2026-07-09 | MEDIUM | Dual V1+V2 WS dispatch (strangler-fig) still live; two parsers/registries to keep in sync | OPEN |
| A-2026-07-10 | MEDIUM | `api.NewRouter` god-constructor: builds services, hub, LiveKit, updater, admin, plugins; spawns goroutines; mounts everything | OPEN |
| A-2026-07-11 | MEDIUM | `ws.Hub` mega-object with post-construction `Set*` wiring ("must be called before Run") | OPEN |
-| A-2026-07-12 | MEDIUM | Abandoned SolidJS beachhead still in-tree; `docs/client-architecture.md` describes the abandoned architecture | DECIDED 2026-07-19 — delete beachhead + retire the stale doc (D6) (see [plans/audit-2026-07-19-decisions.md](plans/audit-2026-07-19-decisions.md)) |
+| A-2026-07-12 | MEDIUM | Abandoned SolidJS beachhead still in-tree; `docs/client-architecture.md` describes the abandoned architecture | CLOSED 2026-07-19 — beachhead, adapters, build plugin, and Solid deps removed; client-architecture.md retired in favor of architecture/client.md |
| A-2026-07-13 | LOW | Dead schema: `sounds` table survives soundboard removal (correction 2026-07-19: `audit_log_v6` is only a transient rename inside migration 003, not a coexisting table) | OPEN |
| A-2026-07-14 | LOW | Scattered client constants (`#5865F2` ×18, `localhost:8443` ×3); 64 timer call sites with manual lifecycle | OPEN |
| A-2026-07-15 | LOW | `docs/plans/security-hardening-remediation.md` partly stale (references deleted `store/postgres.go`) | OPEN |
diff --git a/docs/client-architecture.md b/docs/client-architecture.md
index 5dd144a7..9e03a44a 100644
--- a/docs/client-architecture.md
+++ b/docs/client-architecture.md
@@ -1,280 +1,15 @@
-# Client Architecture: Tauri v2
+# Client Architecture (moved)
-Comprehensive architecture reference for the OwnCord Tauri v2 desktop client. Covers project structure, data flow, component system, and subsystems.
+This document previously described the client architecture, including the
+planned SolidJS migration. That migration was **abandoned** (see CHANGELOG)
+and the beachhead code has been removed from the tree — the client is vanilla
+TypeScript with hand-rolled reactive stores and imperative DOM components.
-## Why Tauri v2
+The current, maintained client architecture document is:
-Tauri v2 uses the OS webview (WebView2 on Windows) so the install is ~10-15 MB and RAM usage is ~30-50 MB. The HTML/CSS mockups become the actual UI code, with CSS handling hover effects, conditional visibility, theming, and animations.
+**[docs/architecture/client.md](architecture/client.md)**
----
-
-## Project Layout
-
-```text
-Client/tauri-client/
-├── src-tauri/ # Rust backend
-│ ├── Cargo.toml
-│ ├── tauri.conf.json # Window size, title, plugins, CSP, updater
-│ └── src/
-│ ├── main.rs # Windows entry point
-│ ├── lib.rs # Tauri Builder: plugins, commands, state
-│ ├── credentials.rs # Win Credential Manager (DPAPI)
-│ ├── commands.rs # Settings store, cert fingerprints, DevTools
-│ ├── ws_proxy.rs # WSS proxy with TOFU cert pinning
-│ ├── livekit_proxy.rs # TCP-to-TLS tunnel for LiveKit signaling
-│ ├── ptt.rs # Push-to-talk via GetAsyncKeyState
-│ ├── tray.rs # System tray icon and menu
-│ ├── hotkeys.rs # Global shortcut registration
-│ └── update_commands.rs # Auto-update check + install
-│
-├── src/ # TypeScript frontend
-│ ├── index.html # Single HTML entry point
-│ ├── main.ts # Bootstrap, router, service wiring
-│ │
-│ ├── styles/
-│ │ ├── tokens.css # CSS custom properties
-│ │ ├── base.css # Reset, scrollbar, typography
-│ │ ├── login.css # ConnectPage styles
-│ │ ├── app.css # MainPage + component styles
-│ │ └── theme-neon-glow.css # Default theme overrides
-│ │
-│ ├── lib/ # Core services (no UI, no DOM)
-│ │ ├── api.ts # REST client (Tauri plugin-http)
-│ │ ├── ws.ts # WebSocket client (Tauri IPC proxy)
-│ │ ├── types.ts # Protocol types (WS + REST + permissions)
-│ │ ├── store.ts # Reactive store factory
-│ │ ├── dispatcher.ts # WS message -> store action router
-│ │ ├── router.ts # In-memory page router
-│ │ ├── livekitSession.ts # LiveKit voice/video session
-│ │ ├── connectionStats.ts # WebRTC stats poller
-│ │ ├── rate-limiter.ts # Sliding-window rate limiter
-│ │ ├── permissions.ts # Bitfield utilities
-│ │ ├── profiles.ts # Server profile CRUD
-│ │ ├── credentials.ts # Credential storage (Tauri IPC)
-│ │ ├── disposable.ts # Component lifecycle cleanup
-│ │ ├── dom.ts # XSS-safe DOM helpers
-│ │ ├── safe-render.ts # Error boundary
-│ │ ├── logger.ts # Structured logger
-│ │ ├── notifications.ts # Desktop notifications
-│ │ ├── tenor.ts # Tenor GIF API v2
-│ │ ├── themes.ts # Theme manager
-│ │ ├── updater.ts # Auto-update
-│ │ ├── reconcile.ts # Keyed DOM list reconciliation
-│ │ ├── icons.ts # Lucide SVG icon factory
-│ │ └── ...
-│ │
-│ ├── stores/ # Reactive state stores
-│ │ ├── auth.store.ts
-│ │ ├── channels.store.ts
-│ │ ├── dm.store.ts
-│ │ ├── messages.store.ts
-│ │ ├── members.store.ts
-│ │ ├── voice.store.ts
-│ │ └── ui.store.ts
-│ │
-│ ├── components/ # UI components
-│ │ ├── MessageList.ts, MessageInput.ts, ...
-│ │ ├── message-list/ # MessageList sub-modules
-│ │ └── settings/ # Settings tab components
-│ │
-│ └── pages/
-│ ├── ConnectPage.ts # Login/register page
-│ ├── MainPage.ts # Main app layout
-│ └── main-page/ # MainPage sub-controllers
-│ ├── SidebarArea.ts
-│ ├── ChatArea.ts
-│ ├── ChannelController.ts
-│ ├── MessageController.ts
-│ └── ...
-│
-├── tests/
-│ ├── unit/ # Vitest unit tests
-│ ├── integration/ # Vitest with mocked WS
-│ └── e2e/ # Playwright E2E tests
-│
-├── vite.config.ts
-├── tsconfig.json
-├── vitest.config.ts
-└── playwright.config.ts
-```
-
----
-
-## Architecture Layers
-
-```text
-+===================================================================+
-| UI Components |
-| (HTML + CSS, vanilla TypeScript DOM manipulation) |
-| Components are factory functions returning { mount, destroy } |
-+===================================================================+
- | | |
- | subscribe() | actions | events
- v v v
-+===================================================================+
-| Reactive Stores |
-| auth | channels | dm | messages | members | voice | ui |
-| Immutable state. Batched notifications via queueMicrotask. |
-+===================================================================+
- ^ |
- | WS events | send()
-+===================================================================+
-| Core Services |
-| ws.ts api.ts dispatcher.ts rate-limiter.ts |
-| livekitSession.ts notifications.ts ptt.ts tenor.ts |
-+===================================================================+
- | |
- | invoke() | listen()
- v v
-+===================================================================+
-| Tauri IPC Bridge |
-+===================================================================+
- | ^
- v |
-+===================================================================+
-| Rust Backend |
-| ws_proxy (WSS + TOFU) livekit_proxy (TCP-to-TLS tunnel) |
-| credentials (Win32 DPAPI) ptt (GetAsyncKeyState polling) |
-| commands (settings store) tray hotkeys update_commands |
-+===================================================================+
-```
-
-Data flows DOWN through layers. Events flow UP via subscriptions. No component directly calls the WebSocket or REST API; they go through stores and controllers.
-
----
-
-## Rust Backend Modules
-
-### ws_proxy.rs -- WebSocket Proxy with TOFU
-
-WebView2 rejects self-signed TLS certificates. All WebSocket traffic routes through Rust. The Rust proxy implements TOFU certificate pinning -- on first connect, the cert fingerprint is stored; on subsequent connects, it is verified.
-
-### livekit_proxy.rs -- LiveKit TLS Tunnel
-
-A local TCP listener proxies LiveKit SDK connections through TLS to the remote server, avoiding self-signed cert issues.
-
-### credentials.rs -- Windows Credential Manager
-
-Uses Win32 Credential Manager APIs. Credentials are stored as DPAPI-encrypted blobs tied to the Windows user account.
-
-### ptt.rs -- Push-to-Talk
-
-Uses `GetAsyncKeyState` for non-consuming key detection. 20ms polling loop on a background thread.
-
-### tray.rs -- System Tray
-
-System tray icon with Show/Hide, Status submenu, and Quit.
-
-### update_commands.rs -- Auto-Update
-
-Dynamic server URL updater endpoint. Update artifacts are verified via Ed25519 signature.
-
----
-
-## Store System
-
-The store factory (`createStore`) provides `getState`, `setState`, `subscribe`, `subscribeSelector`, `select`, and `flush`. State is always immutable. Notifications are batched via `queueMicrotask`.
-
-### Store Responsibilities
-
-| Store | Key State | WS Events Handled |
-|-------|-----------|-------------------|
-| **auth** | token, user, serverName, motd, isAuthenticated | `auth_ok`, `auth_error` |
-| **channels** | channels (Map), activeChannelId | `ready`, `channel_create/update/delete` |
-| **dm** | DM channel list | `dm_channel_open`, `dm_channel_close` |
-| **messages** | per-channel messages, pending sends, hasMore | `chat_message`, `chat_edited`, `chat_deleted`, `chat_send_ok`, `reaction_update` |
-| **members** | member Map, typing indicators | `ready`, `member_join/leave/update/ban`, `typing`, `presence` |
-| **voice** | currentChannelId, voice users, local audio state | `voice_state`, `voice_leave`, `voice_config`, `voice_token` |
-| **ui** | sidebar mode, modals, theme, connection status | `server_restart`, `error` |
-
-Messages per channel are capped at 500. Typing indicators auto-clear after 5 seconds.
-
----
-
-## Component System
-
-Components are factory functions returning `{ mount, destroy }`. `mount()` appends elements to a container; `destroy()` removes DOM, unsubscribes listeners, and clears intervals.
-
-### DOM List Reconciliation
-
-For efficient list updates (member list, channel list), a keyed reconciliation algorithm reuses existing DOM elements, updates in place, and removes stale elements -- preserving hover states, focus, and scroll position.
-
----
-
-## Sidebar Architecture
-
-```text
-+----------------------------------+
-| SERVER HEADER |
-+----------------------------------+
-| DIRECT MESSAGES (3) [+] |
-| Top 3 DMs with unread badges |
-| View all messages link |
-+----------------------------------+
-| TEXT CHANNELS |
-| Category-grouped, collapsible |
-+----------------------------------+
-| VOICE CHANNELS |
-| User avatars in channel |
-+----------------------------------+
-| MEMBERS (collapsible) |
-| Role-grouped, drag-to-resize |
-+----------------------------------+
-| VOICE WIDGET |
-| Mute/deafen/camera/screen/leave |
-+----------------------------------+
-| USER BAR |
-| Settings + quick-switch buttons |
-+----------------------------------+
-```
-
-Two sidebar modes: **"channels"** (full server view) and **"dms"** (full DM conversations list).
-
----
-
-## Chat Area Architecture
-
-The chat area composes: chat header, message list, typing indicator, message input, video grid (overlays when cameras are active), pinned messages panel, and search overlay.
-
-The `ChannelController` manages mounting/destroying per-channel components when the active channel changes.
-
----
-
-## Voice and Video (Client Side)
-
-### LiveKit Session
-
-The `LiveKitSession` class manages the full voice/video lifecycle via LiveKit's `livekit-client` JS SDK.
-
-**Stream Quality Presets:**
-
-| Preset | Camera Resolution | Camera Bitrate | Screen Resolution | Screen Bitrate |
-|--------|------------------|----------------|-------------------|----------------|
-| low | 360p | 600 Kbps | 720p@5fps | 1.5 Mbps |
-| medium | 720p | 1.7 Mbps | 1080p@15fps | 3 Mbps |
-| high | 1080p | 4 Mbps | 1080p@30fps | 6 Mbps |
-| source | 1080p | 8 Mbps | native | 10 Mbps |
-
-### Connection Quality
-
-A 2-second polling interval collects WebRTC stats from both publisher and subscriber PeerConnections. Quality is color-coded: green (<100ms), yellow (100-200ms), red (>200ms).
-
----
-
-## REST API Client
-
-Uses `@tauri-apps/plugin-http` fetch (not browser fetch) to bypass self-signed cert rejection. All requests include `danger: { acceptInvalidCerts: true }` for server URLs only. Third-party fetches use standard cert validation.
-
----
-
-## Dispatcher
-
-`wireDispatcher(ws)` attaches listeners to the WsClient, routing each server message type to the appropriate store actions. Key mappings:
-
-- `ready` -> sets channels, members, voice states, DM channels
-- `chat_message` -> adds message, increments unread, triggers notifications
-- `voice_token` -> starts LiveKit session
-- `presence` -> updates member status
-- `server_restart` -> shows warning banner
-
-See [protocol.md](protocol.md) for complete message type reference.
+See also [docs/architecture/README.md](architecture/README.md) for the full
+blueprint set and its maintenance rule, and decision D6 in
+[docs/plans/audit-2026-07-19-decisions.md](plans/audit-2026-07-19-decisions.md)
+for the removal rationale.
diff --git a/docs/plans/audit-2026-07-19-decisions.md b/docs/plans/audit-2026-07-19-decisions.md
index c0dd9d83..1c4e3dbc 100644
--- a/docs/plans/audit-2026-07-19-decisions.md
+++ b/docs/plans/audit-2026-07-19-decisions.md
@@ -19,7 +19,7 @@ here (and the audit's closure table) as items land.
| D3 | Fate of `Server/store/` (untested abstraction seam) | prior audit #6 | **Remove `store/`**: execute the prior audit's P4 "single data layer" direction. Services call the (sqlc-backed) `db` package directly; tests use in-memory SQLite instead of `MemStore`. | Planned (sequence with/after D2) |
| D4 | Protocol constants sync (`message_types.go` / `protocolTypes.ts` claim a nonexistent `docs/protocol-schema.json`) | A-2026-07-08 | **Create real codegen**: commit an actual `protocol-schema.json` plus a generator that emits the Go and TS constant files (and, ideally, protocol.md's message table), making the "single source of truth" comment true. | **Implemented 2026-07-19**: `docs/protocol-schema.json` + `Server/scripts/genprotocol` + `make protocol-generate`/`protocol-verify` + CI gate. protocol.md table generation deferred to D7. |
| D5 | Client HTTP TLS gap (`allowSelfSigned: true`, no TOFU pinning on the REST path) | A-2026-07-02 | **Next security work**: build the TOFU HTTP proxy in Rust (mirroring `ws_proxy.rs`) as the next security task — highest-priority security item. | Planned |
-| D6 | Abandoned SolidJS beachhead + stale `docs/client-architecture.md` | A-2026-07-12 | **Delete it all**: remove `src/components/solid/`, `solidMount`/`solidAdapter`, `vite-plugin-solid`, and Solid test deps; retire `client-architecture.md` in favor of [docs/architecture/client.md](../architecture/client.md). | Planned |
+| D6 | Abandoned SolidJS beachhead + stale `docs/client-architecture.md` | A-2026-07-12 | **Delete it all**: remove `src/components/solid/`, `solidMount`/`solidAdapter`, `vite-plugin-solid`, and Solid test deps; retire `client-architecture.md` in favor of [docs/architecture/client.md](../architecture/client.md). | **Implemented 2026-07-19** — solid/ dir, solidMount/solidAdapter, setup-solid tests, vite-plugin-solid, jsx tsconfig settings, and solid-js/@solidjs deps all removed; client-architecture.md is now a pointer. |
| D7 | Spec refresh strategy for api.md / protocol.md / schema.md | A-2026-07-03 | **One refresh PR first**, using the audit's §2 conformance matrix as the checklist; afterwards specs are kept current per-PR (see the maintenance rule in [docs/architecture/README.md](../architecture/README.md)). Announcement channels (D1) later update the *fresh* specs. | **Implemented 2026-07-19** — all three specs refreshed against the code (incl. E2EE protocol section, migrations 001–015, profile/blocks/plugin-admin endpoints); reference tables now point at `protocol-schema.json`. |
| D8 | What to implement first | backlog §6 | **Greenlit now: Protocol codegen (D4) + the quick-wins batch** — `LogAudit` error handling (`admin/handlers_backup.go`), contradictory upload `Cache-Control` (`upload_handler.go`), hub inline settings SQL through the data layer (`ws/hub.go`), Hub constructor cleanup (required collaborators into `NewHub`). | **Implemented 2026-07-19** (all four quick wins + D4). Hub cleanup shipped as: race fix — `eventPersister`/`eventStore`/`pluginSink` are now atomic (they were plain fields written by `main.go` after `NewRouter` had already started `Run`); remaining pre-Run setters now reject late calls with an error log instead of racing silently. Note discovered during the work: the discarded-`LogAudit` pattern is repo-wide (23 call sites) — the two tracker-flagged backup handlers are fixed; whether best-effort audit writes stay the convention elsewhere needs a policy decision. |