mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-04 19:58:48 +00:00
* fix: reset-state-on-new-session-click * fix(chat): preserve continuity while session ids settle New conversations were crossing a short but important consistency gap. The route could already point at a newly created session id while the projects payload had not refreshed yet, and realtime/optimistic messages could still be keyed under a provisional id. In that window the UI could stop reading the active session store, briefly render the conversation as missing, and then repopulate it a moment later. That same gap also made duplication more likely. Optimistic local user messages could survive long enough to appear beside the persisted copy, and finalized assistant streaming rows could sit directly next to the server-backed assistant message with the same content before realtime state was cleared. The result was a chat view that felt unstable exactly when a new session was being created. This commit makes session-id reconciliation a first-class part of the chat flow instead of assuming every layer will agree immediately. The session store now understands canonical session aliases and can migrate one conversation from a provisional id to the real id without dropping its in-memory state. The route navigation path can replace the provisional URL entry instead of stacking it in history, and the project/session selection logic keeps a synthetic selected session alive long enough for the sidebar and project payloads to catch up. The practical goal is to keep one visible conversation throughout the whole creation lifecycle: no dead window between websocket events and project refresh, no stale provisional URL after the real id is known, and no extra optimistic/local bubbles when server history catches up. * fix(cli): resolve executable path for Claude CLI on Windows * fix(session-synchronizer): improve session name extraction for Claude and Codex
248 lines
11 KiB
JavaScript
248 lines
11 KiB
JavaScript
import js from "@eslint/js";
|
|
import tseslint from "typescript-eslint";
|
|
import react from "eslint-plugin-react";
|
|
import reactHooks from "eslint-plugin-react-hooks";
|
|
import reactRefresh from "eslint-plugin-react-refresh";
|
|
import { createNodeResolver, importX } from "eslint-plugin-import-x";
|
|
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
|
|
import boundaries from "eslint-plugin-boundaries";
|
|
import tailwindcss from "eslint-plugin-tailwindcss";
|
|
import unusedImports from "eslint-plugin-unused-imports";
|
|
import globals from "globals";
|
|
|
|
export default tseslint.config(
|
|
{
|
|
ignores: ["dist/**", "node_modules/**", "public/**"],
|
|
},
|
|
{
|
|
files: ["src/**/*.{ts,tsx,js,jsx}"],
|
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
plugins: {
|
|
react,
|
|
"react-hooks": reactHooks, // for following React rules such as dependencies in hooks, keys in lists, etc.
|
|
"react-refresh": reactRefresh, // for Vite HMR compatibility
|
|
"import-x": importX, // for import order/sorting. It also detercts circular dependencies and duplicate imports.
|
|
tailwindcss, // for detecting invalid Tailwind classnames and enforcing classname order
|
|
"unused-imports": unusedImports, // for detecting unused imports
|
|
},
|
|
languageOptions: {
|
|
globals: {
|
|
...globals.browser,
|
|
},
|
|
parserOptions: {
|
|
ecmaFeatures: { jsx: true },
|
|
},
|
|
},
|
|
settings: {
|
|
react: { version: "detect" },
|
|
},
|
|
rules: {
|
|
// --- Unused imports/vars ---
|
|
"unused-imports/no-unused-imports": "warn",
|
|
"unused-imports/no-unused-vars": [
|
|
"warn",
|
|
{
|
|
vars: "all",
|
|
varsIgnorePattern: "^_",
|
|
args: "after-used",
|
|
argsIgnorePattern: "^_",
|
|
},
|
|
],
|
|
"no-unused-vars": "off",
|
|
"@typescript-eslint/no-unused-vars": "off",
|
|
|
|
// --- React ---
|
|
"react/jsx-key": "warn",
|
|
"react/jsx-no-duplicate-props": "error",
|
|
"react/jsx-no-undef": "error",
|
|
"react/no-children-prop": "warn",
|
|
"react/no-danger-with-children": "error",
|
|
"react/no-direct-mutation-state": "error",
|
|
"react/no-unknown-property": "warn",
|
|
"react/react-in-jsx-scope": "off",
|
|
|
|
// --- React Hooks ---
|
|
"react-hooks/rules-of-hooks": "error",
|
|
"react-hooks/exhaustive-deps": "warn",
|
|
|
|
// --- React Refresh (Vite HMR) ---
|
|
"react-refresh/only-export-components": [
|
|
"warn",
|
|
{ allowConstantExport: true },
|
|
],
|
|
|
|
// --- Import ordering & hygiene ---
|
|
"import-x/no-duplicates": "warn",
|
|
"import-x/order": [
|
|
"warn",
|
|
{
|
|
groups: [
|
|
"builtin",
|
|
"external",
|
|
"internal",
|
|
"parent",
|
|
"sibling",
|
|
"index",
|
|
],
|
|
"newlines-between": "always",
|
|
},
|
|
],
|
|
|
|
// --- Tailwind CSS ---
|
|
"tailwindcss/classnames-order": "warn",
|
|
"tailwindcss/no-contradicting-classname": "warn",
|
|
"tailwindcss/no-unnecessary-arbitrary-value": "warn",
|
|
|
|
// --- Disabled base rules ---
|
|
"@typescript-eslint/no-explicit-any": "off",
|
|
"@typescript-eslint/no-require-imports": "off",
|
|
"no-case-declarations": "off",
|
|
"no-control-regex": "off",
|
|
"no-useless-escape": "off",
|
|
},
|
|
},
|
|
{
|
|
files: ["server/**/*.{js,ts}"], // apply this block only to backend source files
|
|
ignores: ["server/**/*.d.ts"], // skip generated declaration files in backend linting
|
|
plugins: {
|
|
boundaries, // enforce backend architecture boundaries (module-to-module contracts)
|
|
"import-x": importX, // keep import hygiene rules (duplicates, unresolved paths, etc.)
|
|
"unused-imports": unusedImports, // remove dead imports/variables from backend files
|
|
},
|
|
languageOptions: {
|
|
parser: tseslint.parser, // parse both JS and TS syntax in backend files
|
|
parserOptions: {
|
|
ecmaVersion: "latest", // support modern ECMAScript syntax in backend code
|
|
sourceType: "module", // treat backend files as ESM modules
|
|
},
|
|
globals: {
|
|
...globals.node, // expose Node.js globals such as process, Buffer, and __dirname equivalents
|
|
},
|
|
},
|
|
settings: {
|
|
"boundaries/include": ["server/**/*.{js,ts}"], // only analyze dependency boundaries inside backend files
|
|
"import/resolver": {
|
|
// boundaries resolves imports through eslint-module-utils, which reads the classic
|
|
// import/resolver setting instead of import-x/resolver-next.
|
|
typescript: {
|
|
project: ["server/tsconfig.json"], // resolve backend aliases using the canonical backend tsconfig
|
|
alwaysTryTypes: true, // keep normal TS package/type resolution working alongside aliases
|
|
},
|
|
node: {
|
|
extensions: [".mjs", ".cjs", ".js", ".json", ".node", ".ts", ".tsx"], // preserve Node-style fallback resolution for plain files
|
|
},
|
|
},
|
|
"import-x/resolver-next": [
|
|
// ESLint's import plugin does not read tsconfig path aliases on its own.
|
|
// This resolver teaches import-x how to understand the backend-only "@/*"
|
|
// mapping defined in server/tsconfig.json, which fixes false no-unresolved errors in editors.
|
|
createTypeScriptImportResolver({
|
|
project: ["server/tsconfig.json"], // point the resolver at the canonical backend tsconfig instead of the frontend one
|
|
alwaysTryTypes: true, // keep standard TypeScript package resolution working while backend aliases are enabled
|
|
}),
|
|
// Keep Node-style resolution available for normal package imports and plain relative JS files.
|
|
// The TypeScript resolver handles aliases, while the Node resolver preserves the expected fallback behavior.
|
|
createNodeResolver({
|
|
extensions: [".mjs", ".cjs", ".js", ".json", ".node", ".ts", ".tsx"],
|
|
}),
|
|
],
|
|
"boundaries/elements": [
|
|
{
|
|
type: "backend-shared-type-contract", // shared backend type/interface contracts that modules may consume without creating runtime coupling
|
|
pattern: [
|
|
"server/shared/types.{js,ts}",
|
|
"server/shared/interfaces.{js,ts}",
|
|
], // keep backend modules on explicit shared contract files for erased imports only
|
|
mode: "file", // treat each shared contract file itself as the boundary element instead of the whole folder
|
|
},
|
|
{
|
|
type: "backend-shared-utils", // shared backend runtime helpers that modules may import directly
|
|
pattern: ["server/shared/utils.{js,ts}", "server/shared/claude-cli-path.ts"], // classify the shared utils file so modules can depend on it explicitly
|
|
mode: "file",
|
|
},
|
|
{
|
|
type: "backend-legacy-runtime", // legacy runtime persistence modules used while providers migrate into server/modules
|
|
pattern: [
|
|
"server/projects.js",
|
|
"server/sessionManager.js",
|
|
"server/utils/runtime-paths.js",
|
|
], // provider history loading still resolves session data through these legacy runtime files
|
|
mode: "file",
|
|
},
|
|
{
|
|
type: "backend-module", // logical element name used by boundaries rules below
|
|
pattern: "server/modules/*", // each direct folder in server/modules is treated as one module boundary
|
|
mode: "folder", // classify dependencies at folder-module level (not per individual file)
|
|
capture: ["moduleName"], // capture the module folder name for messages/debugging/template use
|
|
},
|
|
],
|
|
},
|
|
rules: {
|
|
// --- Unused imports/vars (backend) ---
|
|
"unused-imports/no-unused-imports": "warn", // warn when imports are not used so they can be cleaned up
|
|
"unused-imports/no-unused-vars": "off", // keep backend signal focused on dead imports instead of local unused variables
|
|
|
|
// --- Import hygiene (backend) ---
|
|
"import-x/no-duplicates": "warn", // prevent duplicate import lines from the same module
|
|
"import-x/order": [
|
|
"warn", // keep backend import grouping/order consistent with the frontend config
|
|
{
|
|
groups: [
|
|
"builtin", // Node built-ins such as fs, path, and url come first
|
|
"external", // third-party packages come after built-ins
|
|
"internal", // aliased internal imports such as @/... come next
|
|
"parent", // ../ imports come after aliased internal imports
|
|
"sibling", // ./foo imports come after parent imports
|
|
"index", // bare ./ imports stay last
|
|
],
|
|
"newlines-between": "always", // require a blank line between import groups in backend files too
|
|
},
|
|
],
|
|
"import-x/no-unresolved": "error", // fail when an import path cannot be resolved
|
|
"import-x/no-useless-path-segments": "warn", // prefer cleaner paths (remove redundant ./ and ../ segments)
|
|
"import-x/no-absolute-path": "error", // disallow absolute filesystem imports in backend files
|
|
|
|
// --- General safety/style (backend) ---
|
|
eqeqeq: ["warn", "always", { null: "ignore" }], // avoid accidental coercion while still allowing x == null checks
|
|
|
|
// --- Architecture boundaries (backend modules) ---
|
|
"boundaries/dependencies": [
|
|
"error", // treat architecture violations as lint errors
|
|
{
|
|
default: "allow", // allow normal imports unless a rule below explicitly disallows them
|
|
checkInternals: false, // do not apply these cross-module rules to imports inside the same module
|
|
rules: [
|
|
{
|
|
from: { type: "backend-module" }, // modules may depend on shared type/interface contracts only as erased type-only imports
|
|
to: { type: "backend-shared-type-contract" },
|
|
disallow: {
|
|
dependency: { kind: ["value", "typeof"] },
|
|
}, // block runtime imports so shared contracts stay compile-time only instead of becoming hidden shared modules
|
|
message:
|
|
"Backend modules may only use `import type` when importing from server/shared/types.ts or server/shared/interfaces.ts.",
|
|
},
|
|
{
|
|
to: { type: "backend-module" }, // when importing anything that belongs to another backend module
|
|
disallow: { to: { internalPath: "**" } }, // block all direct/deep imports into module internals by default
|
|
message:
|
|
"Cross-module imports must go through that module's barrel file (server/modules/<module>/index.ts or index.js).", // explicit error message for architecture violations
|
|
},
|
|
{
|
|
to: { type: "backend-module" }, // same target scope as the disallow rule above
|
|
allow: {
|
|
to: {
|
|
internalPath: [
|
|
"index", // allow extensionless barrel imports resolved as module root index
|
|
"index.{js,mjs,cjs,ts,tsx}", // allow explicit index.* barrel file imports
|
|
],
|
|
},
|
|
}, // re-allow only public module entry points (barrel files)
|
|
},
|
|
],
|
|
},
|
|
],
|
|
"boundaries/no-unknown": "error", // fail fast if boundaries cannot classify a dependency, which prevents silent rule bypasses
|
|
},
|
|
}
|
|
);
|