feat(workplace): per-project custom working directory - #32
Conversation
Creating a project does nothing when the typed name contains no ASCII word characters — for example a fully Chinese name like "知识工程". handleCreate slugifies the input through [^a-z0-9_-] before submitting, and when everything is stripped it bails out with a bare `return`, so the "+" click appears dead with no hint about what went wrong. Derive the slug live while typing. When the name is non-empty but the slug is empty, mark the input invalid (red outline, tooltip, and a small anchored hint that does not shift the topbar layout) and disable the submit button, so the constraint is visible before the click instead of being swallowed after it. Verified with `npm run typecheck` and `npm run build`; manually checked that Chinese-only input shows the invalid state with the hint, that mixed input like "My App" still creates as "my-app", and that plain ASCII names are unaffected.
Projects were pinned to `{repo_parent}/OpenOPC_workplace/{project_id}`
with no way to relocate them, so agents could not work inside an
existing repository or on another drive.
Resolution is now three-tiered, most specific first:
1. per-project path stored in the new `project_config` table, in that
project's own tasks.db
2. `$OPC_WORKPLACE_ROOT` global override
3. the previous default layout
`get_project_workplace()` gains an optional `custom_path` parameter, so
callers without DB access (tests, preflight) keep the old behaviour
unchanged. The DDL is idempotent and runs on every `initialize()`, so
existing databases gain the table without a migration step.
The engine loads the custom path into a synchronous cache during
initialize() and re-reads it on demand, keeping the existing sync
resolvers (`_resolve_external_workspace`) untouched in signature.
Sticky session workspace roots that merely equal the project workplace
are no longer persisted — they carry no session intent and would pin
the session to a stale path after set-workplace changes; existing
pinned sessions self-heal on their next message.
CLI: `opc project set-workplace/show-workplace/reset-workplace`.
Verified end to end: a claude_code task in a project pointed at an
existing repository runs with cwd inside that repository; set/show/
reset round-trips through the project DB; legacy DBs upgrade in place;
tests/test_engine_session_defaults.py passes.
Surfaces the per-project working directory in the Office UI. Create dialog: the "+" button now opens a centered modal (matching the delete-confirm and org-create conventions) with the project name and an optional workplace path. The name field validates live — input that slugifies to empty (e.g. fully non-ASCII names) shows an inline hint and disables submit, and a preview shows the derived id when it differs from the typed name. Settings panel: a gear button next to the project selector opens a dialog showing the currently resolved path and its source tier (custom / OPC_WORKPLACE_ROOT / default), pre-fills custom paths for editing, and offers reset-to-default. The `project_workplace` envelope is routed through wsClient so the dialog receives the echo. All surfaces use theme tokens (--bg-elevated / --text / --border) so the paper theme stays readable. Ships the rebuilt frontend_dist bundle; README and README.zh-CN document the three-tier resolution order and the CLI commands.
Request changes: establish one theme source of truth and add regression coverageThe implementation direction is sound: the selected Office UI theme is restored from I am requesting changes before merge for the following maintainability and regression-safety issues: 1. Use a single canonical theme registryTheme identity is currently represented independently by the Please define one canonical, immutable theme registry and derive the following from it:
The CSS should consume the same stable identifier contract rather than requiring another independently maintained list. This should replace the duplicated declarations, not add another synchronization layer. 2. Add focused automated regression testsPlease add tests covering:
The tests should exercise public behavior and avoid duplicating the implementation’s theme list in test code. 3. Rebuild the frontend artifact after integrationPRs #31 and #33 both replace the hashed frontend bundle and modify Do not resolve the generated-file conflict by selecting either PR’s existing artifact. With these changes, the feature will be safe to merge and substantially less likely to regress during future theme additions or refactoring. |
Summary
Every project's workplace is currently pinned to
../OpenOPC_workplace/<project_id>/beside the OpenOPC repo, with no way to relocate it. In practice most users want agents to work inside an existing repository (their actual codebase), not in a freshly created empty folder. Today the only workarounds are symlinks or spelling out the path in every prompt.This PR makes the workplace configurable per project, end to end: the path a user sets is the
cwdthe execution agent (claude_code / codex / cursor / opencode) is spawned with.Design
Resolution is three-tiered, most specific first:
project_configtable inside that project's owntasks.db$OPC_WORKPLACE_ROOT— global override; each project resolves to$OPC_WORKPLACE_ROOT/<project_id>../OpenOPC_workplace/<project_id>/layout, unchangedget_project_workplace()gains an optionalcustom_pathparameter; callers without DB access (tests, preflight) keep the old behaviour untouched. The DDL is idempotent and runs on every storeinitialize(), so existing databases gain the table with no migration step. The engine loads the custom path into a synchronous cache duringinitialize(), keeping the existing sync resolvers' signatures unchanged.Sticky session workspace roots that merely equal the project workplace are no longer persisted into
execution_defaults— they carry no session-specific intent and previously froze the session to whatever the workplace was at first message, so laterset-workplacechanges never took effect. Sessions pinned before this rule self-heal on their next message. Roots derived from an explicit output dir keep sticking exactly as before (covered bytests/test_engine_session_defaults.py).Surfaces
Office UI
+opens a centered modal (same conventions as the delete-confirm / org-create dialogs) with project name and an optional workplace path. The name field validates live and previews the derived slug.CLI
Docs: README / README.zh-CN document the resolution order and commands.
Testing
cwdinside that repository (verified against a real session, including one that had been pinned to the old default and self-healed).set-workplace → show-workplace → reset-workplaceround-trips through the project DB; a legacy DB (pre-existing tables, noproject_config) upgrades in place with data intact.pytestfailure set is identical to currentmainbaseline (no new failures);tests/test_engine_session_defaults.pypasses 7/7.npm run typecheckandnpm run buildclean; rebuiltfrontend_distincluded.Stacked on #31
This branch is based on #31 (
fix/project-create-name-feedback) because both touchProjectSelector.tsx— the create-dialog rewrite here supersedes the inline validation added there. Until #31 merges, its commit (d7cfb2b) appears in this PR's diff; once #31 lands, this PR reduces to its own two commits. Merging #31 first is recommended.