mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* refactor(web): adopt lucide-react for the inline Icon component
The hand-rolled `<Icon>` set drifted in stroke weight and proportion across
its 50+ glyphs as new icons were added. Swap the implementation to dispatch
to `lucide-react` while keeping the same `<Icon name="..." size={X} />` API
so the 246 existing call sites stay untouched.
- Adds `lucide-react` as a dependency (tree-shaken; ~30KB gzipped for the
~50 icons we actually import).
- `discord` and `x-brand` keep their bespoke inline SVG paths since lucide
intentionally does not ship brand artwork.
- `spinner` continues to use the existing `.icon-spin` className for its
rotation; under the hood it now renders lucide's `Loader2`.
- New `paw` glyph (lucide `PawPrint`) so the Pets nav item stops sharing
the `sparkles` icon with External MCP.
No behaviour change: the prop surface is identical, fill follows
`currentColor` exactly as before, and aria-hidden / focusable defaults are
preserved. Visual deltas are limited to the strokes themselves (slightly
finer endcaps, more consistent baseline weights) — exactly the
consistency upgrade lucide gives us.
* feat(web): bundle official brand assets for agent icons
`AgentIcon` previously approximated each agent's brand with hand-drawn
SVG (orange Anthropic-ish sparkle, OpenAI-knot ellipses, etc). Replace
those approximations with the real, vendor-published artwork shipped as
static assets under `apps/web/public/agent-icons/`.
- 13 SVG marks sourced from `@lobehub/icons-static-svg` (MIT) — color
variants where the vendor published one (Claude, Codex, Gemini,
Copilot, Qwen, Qoder, DeepSeek, Kimi, Mistral/Vibe), monochrome marks
for the rest (Cursor, OpenCode, Hermes, MiMo, Pi, Kilo).
- 1 PNG mark (Devin) sourced from devin.ai/icon.png, resized to 96×96
via `sips` since Cognition doesn't publish an SVG.
- Each SVG was cleaned (stripped `<title>` brand text and the library's
internal `style="flex:none;..."` ; dropped `width/height="1em"` so
`viewBox` governs sizing) and run through `svgo --multipass`. Total
bundle footprint: ~36 KB for all 17 files, only loaded on the agent
cards that render them.
- `AgentIcon` now resolves brands via a small `ICON_EXT` table and
renders `<img src="/agent-icons/<id>.<ext>">`. Agents without an asset
(`devin` is the lone outlier removed in this commit because PNG; new
agents with no shipped artwork at all) fall back to an initial-letter
pill that reads as "no official mark yet" rather than inventing
brand artwork.
- Removes the `simple-icons` dependency from a previous iteration since
`AgentIcon` was its only consumer.
Public-API stable: `<AgentIcon id={a.id} size={X} />` still accepts the
same prop shape; `AvatarMenu`'s small-size usage continues to work.
* refactor(web): polish entry view + Settings dialog UI for v0.7.0
A sweep over the two surfaces that have the most visual surface area in
the app (the entry sidebar / New Project panel on the left, and the
Settings modal). The work converged on a single neutral palette + a
small set of shared dimensional standards documented in CSS, so future
sections that get added slot into the same rhythm.
New Project panel (apps/web/src/components/NewProjectPanel.tsx +
.newproj* rules in index.css)
- Adds a spec comment block at the top of the .newproj rules listing
the canonical heights (input 30, dropdown 38, compact toggle 36,
popover item 38) and the neutral colour rules.
- Rebuilds PlatformPicker as a DS-picker-style dropdown trigger +
popover (the previous 6-card 2×3 grid was ~280px tall; the dropdown
collapses to a single 38px row with the same multi-select semantics).
- Replaces SurfaceOptions' two heavy `ToggleRow` cards with the new
compact one-line `CompactToggle`; the descriptive hint moves to a
native `title` tooltip.
- Compresses the Fidelity card grid (thumb aspect 16/7 → 16/5, tighter
padding, smaller label).
- Neutralises every selected/active state inside the panel: removes the
orange accent fills and rings from `.newproj-card.active`,
`.newproj-title-badge`, `.compact-toggle.on`, `.toggle-row.on`, the
DS picker popover items + radio/check marks, the trigger open border
and shadow, and the search-bar background. The Create CTA stays the
only orange element on the panel.
- Aligns the project-name input focus state across the sidebar:
border `var(--text)` + 8% black halo (rgba is written out because the
CSS pipeline collapses `color-mix(... 8%, transparent)` down to a
solid `var(--text)`, which would render as a 3px solid black band).
- Switches the body card from `flex: 1 1 auto` to `flex: 0 1 auto` so a
short form variant doesn't leave a white void at the bottom of the
card, and disables overscroll-bounce on the card so a fast scroll
doesn't briefly expose the page-level gray under the white surface.
- Pins the privacy footer below the card with a fixed 0 margin-top +
shorter padding-top so it reads as a label of the card rather than a
centred dialog footer.
Entry sidebar footer (apps/web/src/components/EntryView.tsx +
.entry-side-foot* rules)
- Replaces the X social pill's `external-link` placeholder glyph with a
bespoke filled `x-brand` SVG that mirrors the `discord` mark already
in the icon set.
- Wraps Discord + X in `.entry-side-foot-social` and lets that group
flex-margin to the right of the row, so the two social pills read as
a tight pair instead of a fourth pill stuck to the Pet pill.
- Drops the "unadopted" red dot on the Pet pill (it duplicated the call
to action that the label already carried).
- Shrinks the footer icons to 10px and dims them to 55% / 75% opacity
on hover so the labels are clearly the focal point — `currentColor`
on the lucide-rendered SVGs would otherwise make the glyphs full
black on hover.
- Tightens the env-pill version text cap (180 → 142) so the top row
ends close to the right edge of the Language + Pet group below it.
Settings dialog (apps/web/src/components/SettingsDialog.tsx +
.modal-settings / .settings-* / .seg-* / .agent-* rules)
- Removes the "SETTINGS" kicker eyebrow above each section title (the
big-typography title and modal context already make it redundant).
- Switches the sidebar from a card-per-item layout to ChatGPT-style
single-line pills: hides the `<small>` description, swaps the
sidebar bg from gray to white, makes the active item a gray pill (no
border, no shadow) so all items keep a consistent row height
regardless of state.
- Drops the modal-body's top border (already separated by the
whitespace between modal-head and the body grid) and pins
`.modal-settings { height: min(720px, 100vh - 64px) }` so the
dialog no longer resizes when the user switches between short and
long sections.
- Compresses the Local CLI / BYOK seg-control from a 2-line ~52px card
pair to a 1-line ~42px segmented pill that height-matches the active
sidebar nav-item, and aligns the `.settings-content` padding-top
with `.settings-sidebar` (22 → 16) so the first content row sits
level with the first sidebar item.
- Neutralises agent-card selected state, install/docs link colour, and
protocol-chip active state — same accent-stripping pattern as the
New Project panel.
- Uniform agent-card height via `min-height: 64px` so installed cards
(icon + name + version) align with unavailable cards (icon + name +
not-installed + Install/Docs row).
No prop-API changes, no business-logic edits — this is a pure visual
refactor. Existing tests, providers and daemon contracts are untouched.
178 lines
6.3 KiB
Nix
178 lines
6.3 KiB
Nix
{
|
|
lib,
|
|
stdenv,
|
|
dream2nix,
|
|
nixpkgs,
|
|
system,
|
|
nodejs,
|
|
pnpm_10,
|
|
fetchPnpmDeps,
|
|
pnpmConfigHook,
|
|
src,
|
|
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 (contracts, sidecar-proto,
|
|
# sidecar, platform) 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;
|
|
|
|
# Vendored pnpm store. The hash MUST be pinned on first build:
|
|
# `nix build .#daemon` will fail with the expected hash printed; copy
|
|
# that into `pnpmDepsHash` below. Bump it whenever pnpm-lock.yaml
|
|
# changes.
|
|
pnpmDepsHash = "sha256-/C9tl0CY/vbDr369wVowsrEMxhljX0pnW7kGZbz3Fas=";
|
|
# pnpmDepsHash = lib.fakeHash;
|
|
in
|
|
stdenv.mkDerivation (finalAttrs: {
|
|
inherit pname version src;
|
|
|
|
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;
|
|
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 \
|
|
packages/contracts \
|
|
packages/sidecar-proto \
|
|
packages/sidecar \
|
|
packages/platform \
|
|
apps/daemon
|
|
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/
|
|
|
|
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;
|
|
};
|
|
})
|