mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
* Keep chat typing and web CSS resolution responsive Cache the normalized mention index by entity array and scan only concrete @ positions so the composer does not rescan every file/plugin/skill token on each draft update. Link the shared components package at the root importer so Turbopack/PostCSS can resolve the exported component stylesheet from the web CSS entrypoint. Constraint: Chat composer typing updates run on every keystroke in Electron, and tools-dev/Next evaluates global CSS from the workspace root. Rejected: Debouncing textarea state updates | would make text entry feel stale and would not remove the expensive synchronous parser path. Rejected: Relative-importing package source CSS from apps/web | would bypass the package export contract and hardcode package layout into the app stylesheet. Confidence: high Scope-risk: narrow Directive: Keep inline mention parsing proportional to draft mention markers, and keep package CSS imports resolvable through workspace package links. Tested: pnpm install Tested: pnpm --filter @open-design/web test -- tests/utils/inlineMentions.test.ts Tested: pnpm --filter @open-design/web typecheck Tested: pnpm --filter @open-design/web build Tested: pnpm guard Not-tested: Full web Vitest remains blocked by unrelated CSS selector tests observed before this commit. * Keep composer typing off the render path Move keystroke-time draft updates into the textarea/ref path while deferring React draft rendering, mention overlay work, picker filtering, and localStorage persistence so Electron input echo stays within frame budget. Constraint: Electron chat input was visibly lagging for plain text and mention text while the right iframe stayed responsive. Rejected: Keep the textarea fully controlled | every keystroke still synchronizes the largest ChatComposer render path. Confidence: high Scope-risk: moderate Directive: Keep prompt submission, slash commands, and mention insertion reading draftRef when draft rendering is deferred. Tested: pnpm guard; pnpm --filter @open-design/web typecheck; pnpm --filter @open-design/web build; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.infinite-render.test.tsx; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.context-pickers.test.tsx; pnpm exec vitest run -c vitest.config.ts tests/utils/inlineMentions.test.ts; Electron inspect eval plain text avg 0.03ms max 0.5ms; Electron inspect eval @ path avg 0.03ms max 0.1ms; HTTP smoke status 200 with no components CSS resolve error. Not-tested: Full web Vitest suite and Playwright UI suite. * Keep Electron chat input responsive under heavy projects Constraint: Electron project views can carry large chat/file DOM and Chromium can throttle or defer visible renderer work under tab/window visibility edge cases. Rejected: Re-rendering the composer draft on every plain keystroke | it couples textarea input to mention overlay, resize, storage, and project layout work. Confidence: medium Scope-risk: moderate Directive: Keep plain text input on the textarea/ref path; only commit React draft state immediately for mention, slash, chip, or imperative draft mutations. Tested: pnpm guard; pnpm --filter @open-design/web typecheck; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.infinite-render.test.tsx; pnpm --filter @open-design/desktop test; pnpm --filter @open-design/desktop typecheck; desktop inspect actual project composer with OS SendKeys. Not-tested: Push/PR remains blocked by GitHub 403 for mimicryluden on nexu-io/open-design. * Keep heavy project typing isolated from preview layout Constraint: Electron project panes can carry large DOM histories and preview frames that make textarea paint sensitive to parent layout. Rejected: Further debouncing ChatComposer input state | app-side handler costs were already low while native textarea updates still lagged. Confidence: high Scope-risk: moderate Directive: Keep the composer mounted through the fixed layer/slot pair so future layout changes do not pull typing back into the split-pane flex reflow path. Tested: pnpm --filter @open-design/web typecheck; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.infinite-render.test.tsx tests/components/ChatComposer.context-pickers.test.tsx; pnpm guard; pnpm --filter @open-design/web build; desktop inspect fixed-layer DOM and SendKeys probe avg beforeinput->input 1.4ms max 2.5ms Not-tested: PR publish blocked by GitHub repository permissions if remote still rejects mimicryluden * Keep stale run recovery from hiding generated previews Constraint: Completed daemon runs can outlive the in-memory run registry after a restart. Rejected: Replaying succeeded rows with empty producedFiles | It turns completed messages into failed messages when the registry is gone. Confidence: high Scope-risk: narrow Directive: Only active run statuses should enter daemon reattach recovery. Tested: vitest generation-preview; vitest ProjectView reattach restore; web typecheck; pnpm guard; web build; desktop DOM eval failureText=false iframeCount=2 Not-tested: Fresh provider generation against an external model * Keep composer contracts aligned with latest main Rebase conflict resolution restored the current composer contract while leaving the lower-risk latency fixes in separate layers. Constraint: The branch was rebased over newer workspace/session context composer contracts. Rejected: Keeping the older composer conflict side because it removed current ChatComposer props and broke web typecheck. Confidence: high Scope-risk: narrow Directive: Preserve upstream ChatComposer contracts when rebasing input-latency work. Tested: web typecheck; targeted ChatComposer, inlineMentions, ProjectView generation-preview tests; desktop window-chrome test Not-tested: Full browser interaction after fork push * Fix daemon Nix workspace symlink pruning --------- Co-authored-by: Siri-Ray <2667192167@qq.com>
186 lines
6.8 KiB
Nix
186 lines
6.8 KiB
Nix
{
|
|
lib,
|
|
stdenv,
|
|
dream2nix,
|
|
nixpkgs,
|
|
system,
|
|
nodejs,
|
|
pnpm_10,
|
|
fetchPnpmDeps,
|
|
pnpmConfigHook,
|
|
src,
|
|
workspacePaths,
|
|
makeWrapper,
|
|
python3,
|
|
gnumake,
|
|
pkg-config,
|
|
}:
|
|
# Builds the @open-design/daemon workspace package — produces $out/bin/od.
|
|
#
|
|
# Implementation note on dream2nix:
|
|
# The flake takes `dream2nix` as an input (per the project's Nix
|
|
# contract) but the build itself uses stdenv.mkDerivation. dream2nix's
|
|
# nodejs builders consume npm's package-lock.json — they have no
|
|
# first-class pnpm-lock.yaml + workspace builder yet. When upstream
|
|
# ships one, swap this file for a thin dream2nix module — the inputs
|
|
# are already wired.
|
|
#
|
|
# pnpm version note:
|
|
# `package.json` declares `engines.pnpm: ">=10.33.2 <11"` and pnpm
|
|
# enforces this on `pnpm install` (regardless of `engine-strict`).
|
|
# nixpkgs currently ships 10.33.0, which is rejected. The flake
|
|
# overrides `pkgs.pnpm_10` to fetch the 10.33.2 tarball from npm —
|
|
# see flake.nix for the override and how to bump the hash when
|
|
# `packageManager` advances.
|
|
#
|
|
# Workspace siblings the daemon depends on are built in dependency order
|
|
# before the daemon itself; tsc emits each package's dist/, which is what
|
|
# the daemon resolves at runtime via pnpm's symlinked node_modules.
|
|
let
|
|
pname = "open-design-daemon";
|
|
version = (lib.importJSON ../package.json).version;
|
|
|
|
pnpmDepsHash = (import ./pnpm-deps.nix).daemonHash;
|
|
pnpmWorkspaceFilters = map (workspacePath: "./${workspacePath}") workspacePaths;
|
|
in
|
|
stdenv.mkDerivation (finalAttrs: {
|
|
inherit pname version src;
|
|
|
|
pnpmWorkspaces = pnpmWorkspaceFilters;
|
|
|
|
nativeBuildInputs = [
|
|
nodejs
|
|
pnpm_10
|
|
pnpmConfigHook
|
|
makeWrapper
|
|
# Required to rebuild better-sqlite3's native binding from source.
|
|
# node-gyp drives this via Python; gnumake/pkg-config + the C++
|
|
# compiler from stdenv complete the toolchain.
|
|
python3
|
|
gnumake
|
|
pkg-config
|
|
];
|
|
|
|
pnpmDeps = fetchPnpmDeps {
|
|
inherit (finalAttrs) pname version src;
|
|
hash = pnpmDepsHash;
|
|
pnpmWorkspaces = pnpmWorkspaceFilters;
|
|
fetcherVersion = 3;
|
|
};
|
|
|
|
env.NODE_ENV = "production";
|
|
|
|
# pnpm_10.configHook runs in postConfigureHooks: it unpacks
|
|
# `pnpmDeps`, points pnpm at the unpacked store, and runs
|
|
# `pnpm install --offline --ignore-scripts --frozen-lockfile`.
|
|
# No custom configurePhase needed.
|
|
|
|
buildPhase = ''
|
|
runHook preBuild
|
|
|
|
# Build better-sqlite3's native binding from source.
|
|
#
|
|
# Why from source on Node 24:
|
|
# better-sqlite3 (even 12.9.0, latest as of 2026-05) only
|
|
# publishes prebuilds up to node-v131 (Node 22). No v137
|
|
# (Node 24) prebuild exists. `prebuild-install` would itself
|
|
# fail the GitHub fetch and fall through to a compile, so we
|
|
# skip the download attempt entirely and compile.
|
|
#
|
|
# Why not `pnpm rebuild`:
|
|
# In pnpm 10, `onlyBuiltDependencies` interacts with the
|
|
# "approve-builds" consent gate; `pnpm rebuild <pkg>` silently
|
|
# no-ops in some configurations. Invoke node-gyp directly to
|
|
# sidestep all of that.
|
|
#
|
|
# Env vars:
|
|
# * npm_config_nodedir → use the headers shipped with the
|
|
# nixpkgs nodejs we're already building against, so node-gyp
|
|
# doesn't try to fetch them from nodejs.org/dist (no network
|
|
# in the build sandbox).
|
|
# * npm_config_build_from_source → tell better-sqlite3's
|
|
# prebuild-install fallback chain to skip the CDN download
|
|
# and compile.
|
|
#
|
|
# node-gyp lookup:
|
|
# nixpkgs nodejs ships node-gyp bundled inside npm at
|
|
# ${nodejs}/lib/node_modules/npm/bin/node-gyp-bin. Putting
|
|
# that on PATH gives us a `node-gyp` shim without depending
|
|
# on pnpm-exec resolving from inside better-sqlite3's tree
|
|
# (better-sqlite3 doesn't list node-gyp as a direct dep).
|
|
export npm_config_nodedir=${nodejs}
|
|
export npm_config_build_from_source=true
|
|
export PATH="${nodejs}/lib/node_modules/npm/bin/node-gyp-bin:$PATH"
|
|
|
|
bsq_dir=$(find node_modules/.pnpm -mindepth 2 -maxdepth 4 \
|
|
-type d -path '*/better-sqlite3@*/node_modules/better-sqlite3' \
|
|
-print -quit)
|
|
if [ -z "$bsq_dir" ]; then
|
|
echo "ERROR: better-sqlite3 not found under node_modules/.pnpm — pnpm install may have failed" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Building better-sqlite3 from source at $bsq_dir"
|
|
( cd "$bsq_dir" && node-gyp rebuild --release --build-from-source )
|
|
|
|
# Fail fast if the .node file didn't land where bindings.js
|
|
# looks for it. Without this assertion, a silent skip produces
|
|
# a "valid" derivation that crashes at runtime with
|
|
# "Could not locate the bindings file".
|
|
if [ ! -f "$bsq_dir/build/Release/better_sqlite3.node" ]; then
|
|
echo "ERROR: better_sqlite3.node was not produced at $bsq_dir/build/Release/" >&2
|
|
find "$bsq_dir" -name '*.node' -print >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
for target in ${lib.escapeShellArgs workspacePaths}; do
|
|
pnpm -C "$target" run --if-present build
|
|
done
|
|
runHook postBuild
|
|
'';
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
mkdir -p $out/lib/open-design $out/bin
|
|
|
|
# Copy the whole workspace tree — pnpm's symlinks under node_modules
|
|
# resolve sibling packages by relative paths, so we cannot prune to
|
|
# just apps/daemon.
|
|
cp -r . $out/lib/open-design/
|
|
|
|
# Root devDependencies expose non-daemon workspaces via pnpm symlinks,
|
|
# but the daemon derivation intentionally filters those sources out
|
|
# when they are not needed at runtime. Prune the dangling symlinks from
|
|
# the copied node_modules tree so Nix fixup does not fail on broken
|
|
# links.
|
|
rm -f \
|
|
$out/lib/open-design/node_modules/@open-design/components \
|
|
$out/lib/open-design/node_modules/@open-design/tools-dev \
|
|
$out/lib/open-design/node_modules/@open-design/tools-pack \
|
|
$out/lib/open-design/node_modules/@open-design/tools-serve \
|
|
$out/lib/open-design/node_modules/.bin/tools-dev \
|
|
$out/lib/open-design/node_modules/.bin/tools-pack \
|
|
$out/lib/open-design/node_modules/.bin/tools-serve
|
|
|
|
chmod +x $out/lib/open-design/apps/daemon/dist/cli.js
|
|
|
|
makeWrapper ${nodejs}/bin/node $out/bin/od \
|
|
--add-flags $out/lib/open-design/apps/daemon/dist/cli.js \
|
|
--set NODE_ENV production
|
|
runHook postInstall
|
|
'';
|
|
|
|
passthru = {
|
|
inherit nodejs;
|
|
pnpmDeps = finalAttrs.pnpmDeps;
|
|
};
|
|
|
|
meta = with lib; {
|
|
description = "Open Design daemon — local agent orchestrator + API (`od` CLI)";
|
|
homepage = "https://github.com/nexu-io/open-design";
|
|
license = licenses.asl20;
|
|
mainProgram = "od";
|
|
platforms = platforms.linux ++ platforms.darwin;
|
|
};
|
|
})
|