Edit a project
Everything about a project you can change after it exists — its label, colour, emoji or image, pin state, sidebar group, icon suggestions and session isolation — plus the one thing you cannot edit in place, its folder path.
What it is
Once a project is in the sidebar, every property except its underlying folder path is editable in place. You can rename the sidebar label, change or clear its color bar, give it an emoji or icon, swap or remove a custom image, pin it to the top of the list, move it into a sidebar group (or spin up a new group on the spot), hide the git-branch indicator in session headers, and toggle session isolation. Renaming changes only the display name in Omniscio — your folder on disk keeps its original name. The folder path shows under More options; to point the project at a different location use Move to a new folder…, which re-points the project (and its sessions) to the new folder — or merges it into an existing project already there (covered in Moving a project to a new folder below).
The Edit Project dialog is two-tier so the everyday choices aren't buried under the advanced ones. It opens on a friendly Identity step — the project name, a Group selector (assign the project to a sidebar group, or create a new group inline without leaving the window), and an Appearance control that stays collapsed by default (showing just the current icon and a chevron) so the swatch grids don't dominate the window — click the Appearance row to expand it (color, plus an optional emoji / icon / custom image, with a live preview of how the project will look). A More options → button reveals the second step (folder/move, session isolation, hide-branch, deploy profile, default provider/model, MCP tools, bug-intake), with a ← Back to appearance button to return. The same fast icon/pin/hide-branch/folder actions also live on the project's right-click menu so you don't have to open the dialog for them.
The right-click menu keeps the high-frequency actions flat at the top level — Edit Project, Edit Notes, Pin to Top / Unpin, and the red Remove Project — and groups the less-frequent ones into two hover/click flyout submenus to keep the menu short: a Files submenu (Open Project Docs, Show in Explorer, Copy Path) and an Appearance submenu (Detect Icon, Choose or Replace Icon…, Remove Icon, and the Show/Hide-branch-in-header toggle). Run Recipe remains its own submenu as before. Point at a submenu label (it has a › chevron) and the nested items fly out to the side; click a leaf to run it and the whole menu closes.
Where to find it
How to use it
- Open the Edit dialog. Right-click the project's row in the projects sidebar. The first item in the menu is Edit Project (pencil icon). Click it. (You can also middle-click the row to remove it — that is the delete shortcut, not edit.)
- Change the name. On the opening Identity step the Project Name field is auto-focused. Type a new label and press Enter or click Save Changes. Renaming never touches the folder path — that, and relocating, live under More options → (see Moving a project to a new folder below).
- Pick a group (optional). Just under the name is the Group dropdown — the same control the Add Project dialog uses. It lists every existing sidebar group (divider): pick one to move the project into it, leave it on None to keep it ungrouped, or pick + Create new group… to name a brand-new group without leaving the window. The move is applied when you click Save Changes (not immediately), so Cancel or Esc leaves the project in its current group. If the move fails on the backend, the error shows inline and the window stays open (no false "updated"). Other ways to group projects (drag, or a divider's Add Project) live in reorder-projects.md.
- Set the appearance (color · emoji · icon). The Appearance control sits below the group and is collapsed by default — you see the current icon and a chevron. Click the Appearance row to expand it (this keeps the swatch grids from dominating the window). Expanded, it shows a live preview tile plus:
- Color — the same swatch row as Add Project: click a preset, Custom for a hex value, or None to clear it. The vertical color bar on the sidebar row updates when you save.
- Icon — a curated quick-grid of common emoji and Lucide icons for one-click picks, plus a More emoji… button that opens the full emoji picker (search + every emoji — the same picker used in SMS and Team Chat) so you can choose any emoji, not just the ones in the grid. Click a grid swatch, or open More emoji… and pick one, to set it (it persists immediately and shows in the preview at once); click the No icon (∅) swatch to clear back to the color dot. An emoji/icon takes precedence over the color bar everywhere the project appears.
- Custom image… — opens the native file picker filtered to image types (
.png,.svg,.ico,.jpg,.jpeg,.webp,.gif), defaulting to your project folder. Omniscio stores the absolute path; if you move that file later the icon breaks (silent fallback to the default). - Auto-detect — re-runs the auto-detector, which scans your folder for an icon or logo: the usual spots (
favicon.ico,public/icon.svg, …) plus product-named logos likeomniscio-logo.pngoryoutube-logo.pngsitting inpublic/or the project root. Useful if you added a logo to the repo after creating the project. Even without clicking this, a project that's only showing a generic file-type badge is upgraded to its real logo automatically on the next app start once one is present — while an emoji/icon you picked by hand is never overridden.
- Open More options. Click More options → to reveal the advanced, rarely-touched settings (folder/move, isolation, hide-branch, deploy profile, default provider/model, MCP tools, bug-intake). ← Back to appearance returns to the Identity step.
- Toggle "Hide git branch in header". (Under More options.) Sessions normally show the project's current git branch in their session header (e.g.
main,feat/foo). Flipping this on suppresses that indicator for every session in this project. Useful for branchless folders or when the branch info is just visual noise. There's also a faster shortcut: right-click the project → Appearance → Show branch in header / Hide branch in header flips it without opening the dialog. - Toggle "Isolate new sessions". (Under More options.) When on, every new session in this project runs against its own copy of the folder (a worktree-style sandbox), and changes merge back when the session ends. Off by default. The toggle is wired to
IPC.PROJECT_SET_ISOLATIONand applies on the next session spawn, not retroactively. See Session isolation for what that copy contains and how it is cleaned up. - Set a default model and thinking level (Claude projects). (Under More options.) Projects whose default provider is Claude get two dropdowns — Default model and Default thinking level. Pick a value and every new session in this project starts on it; leave either on Global default to inherit your app-wide setting (Settings → Accounts). They save immediately (you don't need to hit Save Changes) and apply to the next session you start — a session can still override both from its own header before its first message (see start-a-new-session.md). A Default thinking level left on Global default inherits the app-wide thinking default, which can itself vary per model (Settings → Session → Per-model thinking level — e.g. Opus → Max, Haiku → Low); a project thinking default you set here still wins over that per-model default. See per-model-thinking-level.md. The dropdowns are hidden for non-Claude projects (other engines pick their own model) and for virtual projects.
- Pin or unpin. Right-click → Pin to Top moves the project into a fixed pinned section above the dividers and ungrouped projects. The pinned section has its own drag-and-drop order. Right-click again → Unpin sends it back into the regular list at its original
displayOrderposition. (The same Detect/Choose/Remove-icon actions also remain on the right-click Appearance submenu for one-click access without opening the dialog.) - Save and close. The dialog's Save Changes button only writes what actually changed — name, color, provider, and the group (a no-op close keeps your previous data untouched); icon picks persist immediately, independent of Save. A group change is applied as a move (the project hops to the chosen group) separately from the name/color write, and if that move fails the error is surfaced inline and the window stays open instead of a false "updated". Other backend errors (e.g. blank name) also appear inline above the action buttons — if the name is blank when you Save from the Options step, it jumps you back to Identity to fix it. Press Esc or click the backdrop to discard your name/color/group edits.
The Edit dialog hides nothing for virtual projects either — but real-world editing of a virtual project (Skills, Recipes, Cron Jobs, etc.) is mostly cosmetic since their behavior comes from the Omniscio built-ins, not the project record. The whole Files submenu and the whole Appearance submenu are gated to non-virtual projects — for a virtual project those two flyout triggers simply aren't rendered (so every item they would contain — Show in Explorer, Open Project Docs, Copy Path, Detect Icon, Choose or Replace Icon, Remove Icon, Show/Hide branch in header — is absent), and only the flat top-level items (Edit Project, Edit Notes, Pin/Unpin, Remove Project) remain.
Moving a project to a new folder
Moved a repo on disk, or want to relocate a project? Use Move to a new folder… — the button just under the Folder Path in the Edit Project dialog, and the Locate moved folder… button on the red "project folder is missing" banner. It opens a folder picker, then does one of three things automatically based on the old folder and what's at the destination:
- You moved the folder yourself (the old one is gone) → it catches everything up. Omniscio repairs Git's links to the agents' working copies first (inside or outside the folder), moves Claude's own chat files that belong to that folder along with it so each chat resumes exactly, and rewrites every place it saved the old path — sessions, working-copy records and their history, scheduled jobs, bookmarks, task and settings entries — in one all-or-nothing step it can undo. A chat whose file is shared with another folder, or in use, continues from a rebuilt copy of its history instead. Each agent's next turn opens with one line saying where its project now lives. Your own files are never touched.
- The old folder is still there, no project at the destination → it re-points in place. The project's folder changes and every session — plus recipes, schedules, and trackers — comes along, because they're tied to the project's stable id, not its path. Sessions that are still running stay pinned to their original folder so their
--resumekeeps working; only new sessions use the new folder. Nothing is copied or deleted on disk — Omniscio just updates where the project points. - A project already lives at that folder → it offers a guided merge. You get a confirmation showing exactly how many sessions will move; a one-time safety backup of the database is taken first; then everything from this project is re-homed into the one already there, and this (now-empty) project is retired. This is the clean way to fix a split you created earlier by adding the same repo twice. The merge runs as a single all-or-nothing transaction, so it can never leave your data half-moved.
Built-in projects (Skills, Recipes, channels, the KMS vault, etc.) can't be moved — they aren't real folders, so the button is hidden for them.
Moved a whole folder of projects? (in development, Lab-gated) With Move projects on in Settings → Lab, a notice above the project list counts the projects whose folders can't be found — "N projects can't be found — Find them…". Point it at where they are now; it shows what it found for each project with the evidence (same repository, same branches), leaves a same-named folder it can't vouch for unticked, and changes nothing until you confirm. One confirmation catches every ticked project up the same way as above. Omniscio never re-points a project just because a folder with the same name turned up — not at startup, and not when you change the Default Projects Folder; those only report the missing projects. A one-click Move that moves the folder for you is coming next.
Moved the folder but left a junction (or symlink) at the old path? Then there's nothing to re-point. Omniscio treats a project folder reached through a junction as the same folder as its real location — git reports the real path, and Omniscio reads it back in the spelling the project was saved under — so agents' worktrees, Work in Flight, worktree cleanup and session ownership all keep matching. Keep the junction in place for as long as the project stays saved under the old path: remove it and the old path is simply gone, which is the "project folder is missing" case above. There's no need to run git worktree repair because of the junction.
Worktree location (in-development, Lab-gated)
Hidden until you enable Per-project worktree location in Settings → Lab (or it ships on by default). Documented here so it's discoverable while gated.
When session isolation is on, Omniscio creates a git worktree per session. Worktree location — a row under More options, shown only for real git repos — lets you choose WHERE those worktrees are created for this project. The point is disk hygiene: you can keep heavy worktree churn (builds, node_modules, parallel checkouts) off the same physical disk as your database, so they stop competing for I/O. The row shows the current base folder and where the value comes from (a custom location you set · the repo's committed default · the built-in default), a Browse… button (native folder picker), and Reset (back to the default). Pick a folder inside the repo and it warns you — worktrees there make repo-wide searches crawl, so a folder outside the repo is best. The warning persists across reopens, not just right after the pick.
Your choice is private to this machine: it's written as a git-ignored worktree-locations.local.json at the repo root, and Omniscio marks it ignored in the repo's local git config so it can never be committed (your teammates never inherit your path). That one file is read by everything that creates or cleans up worktrees — the app, the dev-pipeline skill, the cleanup reaper, and dependency-sync — so the choice takes effect everywhere, not just inside the app. Reset deletes the file and falls back to the repo's committed default (or the built-in <repo>-worktrees sibling).
How it works. The row is ProjectWorktreeLocationField.tsx: it calls PROJECT_GET_WORKTREE_LOCATION on open, PROJECT_PICK_WORKTREE_LOCATION for the native folder picker, and PROJECT_SET_WORKTREE_LOCATION (with worktreeLocation: null to reset) — handled in project-handlers.ts via worktree-location-override.ts, which writes BOTH the createIn base AND appends it to the scan list (so the reaper + dependency-sync follow the new location) with an atomic temp-then-rename. The three polyglot readers (app / script / PowerShell) prefer the .local.json override over the committed worktree-locations.json, kept byte-identical by a parity test. Full invariants + the tests that lock them: worktree-locations-contract.md. CLI twin: POST /project/:id/worktree-location (body { worktreeLocation: <path> | null }).
AI icon suggestion (in-development, gated)
Hidden until you enable AI Project Icons in Settings → Lab (or it ships on by default). Documented here so it's discoverable while gated.
For a project that only shows a generic type badge (md, js, …) — i.e. no favicon/logo could be detected, so it fell back to the type icon — Omniscio can ask AI to pick a relevant icon for it. When the feature is on, the appearance picker gains a "Suggest with AI" button (next to Auto-detect) that runs a quick one-shot AI call over the project's name + detected type + a short (size-capped) README/description peek and applies the single best-fitting icon from the full free Lucide library (~1,900 icons). A "Fill missing icons with AI" button does the same in bulk for every real project that still shows a type badge, behind a confirm that shows the count first so the cost is never a surprise. It is on-demand only — nothing spends on AI unless you click — and it never overwrites an icon you picked by hand or a real detected logo. An AI-picked icon is a normal lucide:<name> token, so it is "sticky" like a hand pick: a favicon appearing later won't silently replace it (Auto-detect re-detects to override).
How it works. PROJECT_SUGGEST_ICON (single) and PROJECT_SUGGEST_MISSING_ICONS (batch) in project-handlers.ts call icon-suggestion.ts → aiSuggestionService.suggestIconName (the cheap-eligible icon-suggest label; robust parse + one retry + graceful null — ai-icon-suggestion.ts), then write the picked lucide:<name> through the SAME isAllowedProjectIconToken boundary as a manual pick. The full Lucide set validates against a generated name list (lucide-icon-names.generated.ts, regenerated by node scripts/gen-lucide-icon-names.mjs) and renders via lucide's lazy DynamicIcon so the app never static-imports the whole library — see ProjectLucideIcon.tsx. The batch targets exactly projectNeedsAiIcon(project) (a real, non-virtual project whose icon is null or a __type: badge) — the single source of truth shared with the picker's "missing" count so they can't drift. Both channels are desktop-only (blocked over the mobile/web WS bridge) and gated via isUnreleasedFeatureVisible('ai-project-icons'). Full invariants + the tests that lock them: project-icon-token-contract.md (I6).
How it behaves
How it works
The dialog is EditProjectDialog.tsx, portaled to document.body so the modal isn't clipped by the sidebar's backdrop blur. It renders inside ProjectListItem.tsx and opens when the user picks Edit Project from the right-click portal menu (also defined in ProjectListItem.tsx). On submit, the dialog calls useProjectStore().updateProject(), which invokes the PROJECT_UPDATE IPC channel; the handler in project-handlers.ts validates against updateProjectSchema from ipc-schemas.ts, refuses path changes pointing at non-existent folders, then runs updateProject() in queries-projects.ts — a buildSetClause over the editable columns (name, folder_path, color, icon_path, hide_branch_in_header) and a single UPDATE projects SET ... WHERE id = ? AND is_deleted = 0. Booleans coerce to 0/1 before reaching better-sqlite3, which doesn't accept JS booleans on integer columns. The handler emits PROJECTS_CHANGED so the renderer re-fetches. The per-project default model and default thinking level are the exception to that PROJECT_UPDATE path: like the per-project MCP defaults, they aren't projects columns but entries in the projectDefaultModels / projectDefaultThinkingLevels settings maps (keyed by project id), so the dialog persists them immediately via a silent settings:update rather than through PROJECT_UPDATE. At spawn, Omniscio resolves session override → project default → global default. The Group dropdown is the shared ProjectGroupPicker.tsx — the same control Add Project renders (extracted so the two dialogs can't drift). It's a third exception to the PROJECT_UPDATE path: a group change routes through useProjectStore().moveProjectToDivider() → the PROJECT_MOVE_TO_DIVIDER channel, applied on Save separately from the name/color write. Because moveProjectToDivider optimistically rolls back and sets a store error rather than throwing on a DB-level failure, the dialog re-reads useProjectStore.getState().error after the move (mirroring its post-updateProject check) and surfaces it inline — so a failed move keeps the window open instead of firing a false "updated" toast. The Appearance section is collapsed behind a ProjectIcon + ChevronDown disclosure button (default-collapsed via a showAppearance state) that reuses the existing ProjectAppearancePicker unchanged when expanded.
The Files / Appearance flyouts are a purely presentational regrouping. They render through a small reusable ProjectMenuSubmenu component (its own sibling file ProjectMenuSubmenu.tsx, composed by ProjectListItem): a role="menuitem" trigger button with aria-haspopup="true" / aria-expanded, a › (ChevronRight) affordance, and the original menu items moved verbatim into its children. The trigger's onClick sets the open flag to true (it is an open, not a toggle, so a programmatic click in tests deterministically reveals the children); a 200 ms close timer on mouse-leave and a viewport-edge clamp via getBoundingClientRect() handle the hover UX. No IPC channel, handler, schema, or queries-projects.ts call changed — the icon/branch/path actions still invoke exactly the same channels they did when they were flat; only their DOM nesting moved. The non-virtual gate is the same !isVirtualProject(project.folderPath) guard that previously wrapped each item individually, now hoisted to wrap the whole submenu.
Pin/unpin go through the dedicated PROJECT_PIN and PROJECT_UNPIN channels, which call pinProject(id, true|false) in queries-projects.ts — that flips is_pinned and bumps updated_at. The renderer's listProjects() query orders by is_pinned DESC, display_order ASC, which is why pinned projects always render at the top of the list. Icon mutations route to four channels (PROJECT_DETECT_ICON, PROJECT_CHOOSE_ICON, PROJECT_CLEAR_ICON, PROJECT_SET_ICON_TOKEN) — all four write the single icon_path column. The choose-icon handler opens an Electron dialog.showOpenDialog filtered to image extensions, parented to BrowserWindow.getFocusedWindow() so the picker appears in front of the Omniscio window (without the parent, Windows opens the dialog behind the app), and stores the resulting absolute path. The same parent-window pattern applies to DIALOG_OPEN_FOLDER and DIALOG_OPEN_FILE. The emoji/Lucide pick is PROJECT_SET_ICON_TOKEN: icon_path now stores either a real image path OR a self-describing token (emoji:📥 / lucide:Inbox). The curated grid sends a built-in token; the More emoji… button opens the shared full emoji picker (EmojiPickerPopover) so any emoji can be chosen. Because that column is read on the privileged project-icon:// origin, the renderer is forbidden from writing a raw path (RT-F082) — so this channel accepts only a self-describing token: any genuine emoji (validated by Unicode shape via isAllowedProjectIconToken, so the whole picker works) or any real Lucide name — the full library, checked against a generated name list (the AI-suggestion feature below picks from it; the manual quick-grid still offers just the curated subset) — never a raw path. The icon-maintenance sweep skips tokens so it never mistakes one for a missing file and wipes it. Full invariants + the tests that lock them: project-icon-token-contract.md. The hide-branch toggle on the right-click menu is the same PROJECT_UPDATE call as the dialog, just inlined with the inverted current value: ipc.invoke(IPC.PROJECT_UPDATE, { id, hideBranchInHeader: !project.hideBranchInHeader }). Session isolation is its own IPC.PROJECT_SET_ISOLATION channel that flips isolation_enabled immediately when the dialog toggle changes — independent from the form's Save button so users see the worktree behavior on the very next spawn.
Move to a new folder is a separate flow, not part of PROJECT_UPDATE. The button calls the PROJECT_RELOCATE channel via the shared useRelocateProjectAction.ts hook (reused by both the Edit dialog and the folder-missing banner) after a native folder picker (PROJECT_PICK_RELOCATE_FOLDER). When the old folder is gone and the destination is free, relocate-project-service.ts hands the move to the catch-up engine (catch-up.ts, the same one the Find window and POST /projects/locate run): Git re-link, chat-file carry, then every column, container and setting on the saved-path list (saved-paths.ts, a build guard fails on a new path field left off it) rewritten in one bounded transaction after an undo log — the subsystem map is relocate-recreate-folder.md, its rules move-projects-contract.md. Otherwise the handler routes to the engine in relocate-project.ts: an empty destination re-points folder_path in place (sessions follow via their stable project_id; still-live sessions are pinned to the old cwd for --resume safety, and the few rows keyed by the path STRING — MCP overrides, council paths — are swept). An occupied destination is a two-phase, backed-up MERGE (a merge-needs-confirm preview first, then a VACUUM INTO backup, then one atomic transaction) that re-parents every project-scoped row into the survivor — the row set is derived by COLUMN NAME so a future project_id table can't silently be missed (a build-failing coverage guard enforces it), with "prefer the survivor on a uniqueness conflict" via UPDATE OR IGNORE + DELETE. Full invariants + the test that locks each: project-relocate-contract.md.
For agents
CLI parity
The same cosmetic edits the dialog makes (rename, recolor including clearing, icon path, pin/unpin, hide-branch, move-to-divider) are also reachable over HTTP via PATCH /project/:id on the CLI control server. The CLI route accepts the same partial body the IPC handler does — name, color (nullable, pass null to clear the tint), hideBranchInHeader, isPinned, and dividerId (nullable, pass null to remove from the current group). iconPath is not accepted: the route rejects it with 400 and names the way to set an icon for real — POST /project/:id/icon with an emoji:/lucide:/brand: token, or POST /project/:id/detect-icon to derive one from the project folder. A caller-supplied path stays refused because projects.icon_path feeds the privileged project-icon:// protocol (RT-F082). All cosmetic edits apply immediately (no approval queue) because they're revertible personal preferences with no destructive cascade. Detect-icon, choose-icon (file picker), and isolation toggle are dialog-only — the CLI surface only exposes fields that map to the underlying projects columns, not flows that need an Electron dialog or a worktree spawn pipeline. See cli-control.md "PATCH /project/:id" for the full schema.
Catching up moved projects has its own route while Move projects is on in Lab: POST /projects/locate (full-trust token). With searchIn (an absolute folder) and projectIds and no moves, or with dryRun: true, it only answers with the proposals and their evidence; sending the reviewed moves ([{ projectId, to }], each project once) plus the optional parent applies them and answers with what was caught up, refused, and left for a rebuilt chat copy.
Related
- add-a-project.md — adding a project (sets the same name/color/icon fields the Edit dialog tweaks afterwards)
- delete-a-project.md — removing a project from the sidebar (different from editing, soft-delete with undo)
- reorder-projects.md — pin, drag, and group order changes
- branch-header.md — what the git-branch indicator on session headers is and why you'd hide it (forward link — separate page covering the indicator itself in detail)
- projects-sidebar.md — the Omniscio built-ins divider that owns the virtual projects you can also edit
- cli-control.md — the CLI control server and its main endpoints, including
PATCH /project/:idfor the same cosmetic edits this dialog makes - slow-computer.md — why worktree churn can contend for disk I/O (the reason the Worktree location control exists)
Last verified 2026-09-30