Skip to content

feat: add workspace creation with git init and fix folder picker - #7

Open
dcmaf wants to merge 4 commits into
prxshetty:mainfrom
dcmaf:feat/create-new-workspace
Open

dcmaf wants to merge 4 commits into
prxshetty:mainfrom
dcmaf:feat/create-new-workspace

Conversation

@dcmaf

@dcmaf dcmaf commented Sep 14, 2026

Copy link
Copy Markdown
Contributor
  • Add workspace creation in FileStorageService initializing standard project outline (chapters/, characters/, styles/, prompts/, outputs/) and essential starter templates (CHAPTERS.md, chapter-1.md, CHARACTERS.md, protagonist.md, STYLES.md, style guides, story_state.yaml).
  • Add optional Git repository initialization with .gitignore generation, automatically disabled when Git is not available in PATH.
  • Add POST /api/workspace/create and GET /api/workspace/git-status endpoints.
  • Unify native folder picker into a single dialog with foreground focus
  • Add "Create New Workspace" section to General settings in SettingsModal.
  • Add a workspace switcher icon button next to the New Folder button at the top of FileSidebar.
  • Add unit tests covering workspace creation, file generation, Git init, and API endpoints in tests/test_workspace_create.py. Generated by Antigravity.

@prxshetty prxshetty left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — the workspace scaffolding + opt-in git init is the right feature, and the double-dialog picker fix is real. Before merge it needs a rebase + a few guards (reviewed against main @ 15b6cf0, after #5/#6 landed):

Blockers

  1. Stale hunks — rebase needed. api/routers/assist.py (Popen + watcher thread) and api/services/harness_env.py (agy/npm PATH) duplicate #5. #5 landed as PATH-only with async kept, so please rebase onto origin/main and drop both hunks from this branch.

  2. POST /create scaffolds any absolute path with no validation. Please require absolute path, deny system/dot dirs (e.g. ~/.ssh), and refuse non-empty dirs without an explicit flag.

  3. If the target is already inside a work tree, git init creates an embedded repo. Please git rev-parse --show-toplevel (timeout=5) first: if inside a tree, skip init/add/commit and return {already_tracked: True, parent}. Fresh dirs (rev-parse fails) proceed to init normally — that case is untouched. We kind of need to make sure this caters to both users who want privacy vs users who want their workspaces git tracked.

  4. All git calls need timeout=10; always-ensure .gitignore (not only-if-missing); treat commit non-zero (missing user.name/email) as {committed: False, error}, not fatal. Move update_settings(linked_workspace_dir) out of create_workspace() so a git failure can't leave the app pointed at a half-built workspace. Don't leak absolute paths via detail=str(e).

  5. UI default. Backend init_git defaults False (local-first) but SettingsModal.tsx pre-checks with useState(true). Please flip to false and surface the already_tracked message ('Already inside , skipped init').

** If you are using Antigravity for this one, below is an overview and use a better model !**

  • Pure-Tk picker: the osascript/zenity fallback is unreachable behind Tk-first — delete it.
  • Dedupe handleBrowseExisting/handleBrowseNew into one hook; unify linkStatus/createStatus notices.
  • Hoist is_git_available import; drop dead import warnings.
  • Extend tests/test_workspace_create.py with repo-root + nested-subdir already_tracked cases.

You could also extend the workspace tracking feature to track file states as well and indicate them in the FileSidebar.tsx as well if you plan on it or a sync button repalcing the open workspace button to sync ( add + commit ? ) to the git origin that the workspace has, if there isnt we hide it or something. There is also some workspace dedup needed which i can review once the above changes pulls through.

@dcmaf

dcmaf commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review, I'll get Antigravity to work on implementing the changes. I'd forgotten that I did a PR for this and was about to send one for a larger set of changes which do include the file state tracking, staging and commit, diff highlighting, restoring changed files (with snapshot files used for non-git workspaces), auto-doc save, telemetry viewer, and a few more things. I merged the workspace create into that branch and have rebased it on latest main. Let me know if you want this create workspace stuff cleaned up as a separate PR. Otherwise, I'll focus on the larger branch and we can delete this PR.

Here is a screenshot showing some of these changes:

Screenshot 2026-09-15 220602

@prxshetty

Copy link
Copy Markdown
Owner

Thanks, and appreciate the pace — the workspace-create rebase plus the broader set (state tracking, staging/commit, diff, restore/snapshots, auto-save, telemetry) is ambitious, and I can already see a couple of ideas from my own todo list implemented here, so you may have saved me some time.

One project rule before the larger branch goes up: tracking anything about a story is strictly opt-in, and the UI must never misrepresent the default. The backend in this PR defaults init_git to false, but the frontend pre-checks the box — so checked or not, the user gets a git-initialized story, which misrepresents what we're building. Most writers work across contexts that must stay private, so nothing about story content or titles gets tracked, posted, or sent anywhere unless the user explicitly opts in — and the unchecked state has to actually mean off. (Git identity/remote login is a separate issue from local init and gets its own PR.) I like minimalism as well.

Concretely:

  • Keep feat: add workspace creation with git init and fix folder picker #7 alive, push the 5 blockers onto it (smallest diff) — including the initGit default flip to false so the checkbox is honest.
  • Split the larger work into stacked PRs (state tracking → staging/commit → diff → restore → auto-save → telemetry).
  • Confirm the restore rule before building further: git repos use git, non-git uses snapshots — never both on one workspace.
  • I'm currently working on image support, done in a way that adds no context bloat for models — will share separately.

Also, I see you're on Sand — if you haven't tried them yet, give the others a spin. I like Blue Note.

@dcmaf

dcmaf commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, I'll update #7 and then stack the rest of the changes. Definitely have that git defaulted to unchecked on the create workspace. Yes, git and snapshots are mutually exclusive. I understand the privacy angle and minimalism design.

I'm not familiar with Sand and Blue Note?

@dcmaf
dcmaf force-pushed the feat/create-new-workspace branch from e6686b9 to 22a0dc7 Compare September 16, 2026 16:34
@dcmaf

dcmaf commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

@prxshetty I have rebased on main and updated the PR with the requested changes. I'll split the larger changes up into separate PRs. I don't think I can use the stack feature for cross-fork PRs, but I could push them as drafts that you could rebase and merge as you got to them. Alternatively, I can push them sequentially after each review and merge.

I'm looking forward to seeing what you are doing for image support!

@prxshetty

Copy link
Copy Markdown
Owner

@dcmaf added image support. let me know how it goes

@dcmaf
dcmaf force-pushed the feat/create-new-workspace branch 2 times, most recently from aa564fc to f97b638 Compare September 17, 2026 04:09
@dcmaf

dcmaf commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

@prxshetty The image support is cool, would definitely let me do what I was looking to do. Are you considering adding integration with image generating AI? I know that can be a can of worms - there are several plugins for it available in SillyTavern. Can you add having shift-drag on a corner maintain aspect ratio when resizing?

I've rebased this PR on latest main and verified the image inclusion working in my branch. I also added creation of the assets directory when creating a new workspace.

- Add workspace creation in `FileStorageService` initializing standard
  project outline (`chapters/`, `characters/`, `styles/`, `prompts/`, `outputs/`)
  and essential starter templates (`CHAPTERS.md`, `chapter-1.md`, `CHARACTERS.md`,
  `protagonist.md`, `STYLES.md`, style guides, `story_state.yaml`).
- Add optional Git repository initialization with `.gitignore` generation,
  automatically disabled when Git is not available in PATH.
- Add `POST /api/workspace/create` and `GET /api/workspace/git-status` endpoints.
- Unify native folder picker into a single dialog with foreground focus
- Add "Create New Workspace" section to General settings in `SettingsModal`.
- Add a workspace switcher icon button next to the New Folder button at the
  top of `FileSidebar`.
- Add unit tests covering workspace creation, file generation, Git init,
  and API endpoints in `tests/test_workspace_create.py`.
Generated by Antigravity.
- Validate POST /create paths: require absolute paths, deny system and
  dot directories (e.g. ~/.ssh, C:\Windows), and reject non-empty
  directories without explicit force=true.
- Prevent embedded git repos: check `git rev-parse --show-toplevel`
  (timeout=5) before init; return {already_tracked: True, git_parent}
  and skip init/add/commit if already inside a work tree.
- Add timeout=10 to all git subprocess commands; always write .gitignore
  on init; handle missing git author identity gracefully without failing.
- Move update_settings() out of create_workspace() so creation failures
  cannot leave the application linked to a half-built directory.
- Simplify folder picker to a pure-Tkinter dialog, removing unreachable
  OS-specific CLI fallback code and dead imports.
- Sanitize router error responses to avoid leaking internal filesystem paths.
- UI: set local-first initGit default to false, deduplicate folder browse
  handlers into a shared browsePicker helper, and surface already_tracked
  status via an info banner.
- Add unit tests covering dot/sensitive paths, force flags, and root/nested
  already-tracked git repositories (13/13 passing).
Create the directory used to store image assets when creating a new workspace.
@dcmaf
dcmaf force-pushed the feat/create-new-workspace branch from 71add88 to 9778ebe Compare September 18, 2026 03:48
@dcmaf
dcmaf requested a review from prxshetty September 18, 2026 03:50

@prxshetty prxshetty left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the rebase + hardening — verified the 5 blockers are in:

No more assist.py / harness_env.py hunks, clean 5-file diff.
2-4. Absolute/dot/sensitive + force flag, rev-parse already_tracked, timeouts + always-gitignore + non-fatal commit + update_settings moved + sanitized detail — all present.
initGit defaults false + already_tracked banner — present. Picker is pure-Tk, browsePicker deduped, import hoisted.
3 small gaps before I can merge:

  1. /var block breaks macOS temp (3 tests fail locally).
    _SENSITIVE_PATH_PREFIXES includes Path("/var") (api/services/file_storage.py:36). Router resolves both sides (_is_subpath, api/routers/workspace.py:75-82,141-144). On macOS TMPDIR=/var/folders/... resolves to /private/var/... vs /private/var -> match -> 400 "not allowed".
    Repro on macOS: pytest tests/test_workspace_create.py -> test_api_create_workspace, non_empty_rejected_without_force, non_empty_allowed_with_force fail. CI stays green because backend job uses pytest ... 2>/dev/null || echo. Suggest narrowing /var to /var/log, /var/lib etc. or exempting tempfile.gettempdir().

  2. assets/ not scaffolded in create_workspace.
    9778ebe adds (workspace_dir / "assets").mkdir to _ensure_workspace only, not to create_workspace(). New workspaces still lack assets/ which #9 expects. One-liner in create_workspace alongside chapters/characters/styles/prompts/outputs.

  3. Relative-path check is dead code.
    api/routers/workspace.py:127-131 resolves first, then checks is_absolute() — resolve() always returns absolute, so "relative/path" becomes /cwd/relative/path and passes. The existing test only posts " " (hits empty-check). Suggest checking Path(raw).expanduser().is_absolute() before resolve().

Note: keeping import warnings is correct — still used at file_storage.py:49, so ignore my earlier drop-dead-import note.

Once 1-3 are patched I will merge. No action needed on the stacked ideas (state tracking / staging / diff / restore / auto-save / telemetry) — keep those for follow-up PRs.

@dcmaf

dcmaf commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the rebase + hardening — verified the 5 blockers are in:

No more assist.py / harness_env.py hunks, clean 5-file diff. 2-4. Absolute/dot/sensitive + force flag, rev-parse already_tracked, timeouts + always-gitignore + non-fatal commit + update_settings moved + sanitized detail — all present. initGit defaults false + already_tracked banner — present. Picker is pure-Tk, browsePicker deduped, import hoisted. 3 small gaps before I can merge:

  1. /var block breaks macOS temp (3 tests fail locally).
    _SENSITIVE_PATH_PREFIXES includes Path("/var") (api/services/file_storage.py:36). Router resolves both sides (_is_subpath, api/routers/workspace.py:75-82,141-144). On macOS TMPDIR=/var/folders/... resolves to /private/var/... vs /private/var -> match -> 400 "not allowed".
    Repro on macOS: pytest tests/test_workspace_create.py -> test_api_create_workspace, non_empty_rejected_without_force, non_empty_allowed_with_force fail. CI stays green because backend job uses pytest ... 2>/dev/null || echo. Suggest narrowing /var to /var/log, /var/lib etc. or exempting tempfile.gettempdir().
  2. assets/ not scaffolded in create_workspace.
    9778ebe adds (workspace_dir / "assets").mkdir to _ensure_workspace only, not to create_workspace(). New workspaces still lack assets/ which Feat/margin image assets #9 expects. One-liner in create_workspace alongside chapters/characters/styles/prompts/outputs.
  3. Relative-path check is dead code.
    api/routers/workspace.py:127-131 resolves first, then checks is_absolute() — resolve() always returns absolute, so "relative/path" becomes /cwd/relative/path and passes. The existing test only posts " " (hits empty-check). Suggest checking Path(raw).expanduser().is_absolute() before resolve().

Note: keeping import warnings is correct — still used at file_storage.py:49, so ignore my earlier drop-dead-import note.

Once 1-3 are patched I will merge. No action needed on the stacked ideas (state tracking / staging / diff / restore / auto-save / telemetry) — keep those for follow-up PRs.

@prxshetty Thanks - I'll knock those out and update tonight. I should have the next PR ready to go pretty quickly after that.

- Narrow sensitive path prefixes to allow macOS TMPDIR (/var/folders) and /tmp
- Scaffold missing assets/ directory in create_workspace
- Fix dead-code validation by checking raw relative paths before resolve()
- Add case-insensitive subpath containment check for Windows and macOS
- Use shutil.which() for robust platform-agnostic Git binary resolution
- Make workspace path input placeholders in SettingsModal platform-adaptive
- Add unit tests for assets/ directory, relative paths, and temp folder creation
@dcmaf

dcmaf commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

@prxshetty Those issues should be fixed now along with some additional changes for cross-platform compatibility.

@dcmaf
dcmaf requested a review from prxshetty September 19, 2026 01:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants