diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..4c33324 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Something isn't working +title: '' +labels: bug +assignees: '' +--- + +**Describe the bug** +A clear description of what went wrong. + +**To reproduce** +Steps to reproduce the behavior: +1. ... +2. ... + +**Expected behavior** +What you expected to happen instead. + +**Environment** +- Session Manager version: (Menu → About, or `brew info`) +- Install method: Homebrew / release DMG / built from source +- macOS version: +- Claude Code version: (`claude --version`) + +**Screenshots / logs** +If applicable, add screenshots or relevant log output. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..8dcdd8c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea or improvement +title: '' +labels: enhancement +assignees: '' +--- + +**What problem does this solve?** +Describe the problem or friction you're hitting. + +**Proposed solution** +What you'd like to happen. Mockups or examples welcome. + +**Alternatives considered** +Any workarounds you're using today, or other approaches that could work. + +**Willing to implement it?** +Let us know if you'd like to submit a PR for this yourself — see [CONTRIBUTING.md](../../CONTRIBUTING.md). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4c94d68 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ +## What does this PR do? + + + +## How was it tested? + + + +## Checklist + +- [ ] `npm test` passes +- [ ] `npm run build` passes +- [ ] Updated `docs/wiki/` if this changes how a feature behaves diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..13c1430 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,63 @@ +# Contributing to Session Manager + +Thanks for your interest in contributing! Session Manager is a small project, so the process is deliberately lightweight. + +## How contributions work + +- **Found a bug?** [Open a bug report](https://github.com/ryj-dev/session-manager/issues/new?template=bug_report.md). Include your macOS version, app version, and steps to reproduce. +- **Want a feature?** [Open a feature request](https://github.com/ryj-dev/session-manager/issues/new?template=feature_request.md) first, before writing code. This avoids you spending time on something that might not fit the project's direction. +- **Have a fix or feature ready?** Fork the repo, create a branch, and open a pull request against `main`. + +For anything beyond a small fix (typos, obvious one-liners), please open an issue first so we can discuss the approach. + +## Development setup + +Requirements: + +- macOS (the app currently targets macOS; arm64 is the primary platform) +- Node.js 20+ +- [Claude Code](https://claude.com/claude-code) installed, since the app manages Claude Code sessions + +```sh +git clone https://github.com//session-manager.git +cd session-manager +npm install +npm run dev # launches the app with hot reload via electron-vite +``` + +Other useful commands: + +```sh +npm test # run unit tests (node --test over src/main/*.test.ts) +npm run build # type-check and build all processes +npm run dist:mac # produce a distributable .dmg in release/ +``` + +## Project layout + +``` +src/main/ Electron main process — PTY/session management, hook server, + MCP server, pipeline orchestration, persistence +src/preload/ Context-bridge API between main and renderer +src/renderer/ React UI — graph view, terminals, canvas, settings +docs/wiki/ Bundled feature wiki (shipped read-only in the app) +resources/ Static assets bundled into the app +scripts/ Build and release helpers +``` + +## Pull request guidelines + +- **Keep PRs focused.** One fix or feature per PR is much easier to review than a grab-bag. +- **Match the existing style.** The codebase is TypeScript throughout; follow the conventions of the file you're editing rather than introducing new patterns. +- **Add or update tests** when you change logic in `src/main/` that has existing test coverage (`*.test.ts` files). UI changes don't currently have automated tests — describe how you verified them manually in the PR. +- **Run before pushing:** `npm test` and `npm run build` should both pass. +- **Describe the "why".** In the PR description, explain what problem this solves and how you tested it. Screenshots or short recordings are very welcome for UI changes. +- **Update the wiki if behavior changes.** If your change alters how a feature works, update the matching article in `docs/wiki/` — it ships inside the app as the authoritative feature docs. + +## Reviews and releases + +PRs are reviewed as time permits — this is a spare-time project, so please be patient. Merged changes ship in the next tagged release (versioned `v0.x.y`, distributed via GitHub Releases and Homebrew). + +## License + +By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE) that covers the project. diff --git a/README.md b/README.md index d35222a..cf26fd6 100644 --- a/README.md +++ b/README.md @@ -90,9 +90,11 @@ All keyboard shortcuts are configurable. Open **Settings → Keyboard shortcuts* Other settings worth knowing about live in the same panel: base projects directory, terminal pairing mode (off / split / overlay shell alongside Claude), auto-mode defaults for spawned sessions, message popup behavior, and per-integration disable toggles. -## Reporting issues +## Reporting issues & contributing -Open an issue at . +Open an issue at — there are templates for bug reports and feature requests. + +Want to contribute a fix or feature? See [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow (fork → branch → PR) and development setup. ## Credits diff --git a/package-lock.json b/package-lock.json index 590920f..567850e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "session-manager", - "version": "0.1.2", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "session-manager", - "version": "0.1.2", + "version": "0.2.2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 2f83789..672b5b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "session-manager", - "version": "0.2.1", + "version": "0.2.2", "description": "Desktop app for managing multiple Claude Code sessions", "author": "Ry J", "license": "MIT", diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 763c12a..6be27b2 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -86,6 +86,12 @@ function attachSessionListeners( } export function registerIpcHandlers(opts: { reinstallMcp: () => void }): void { + // Real quit (renderer Cmd+Q fallback). app.quit() fires before-quit, which + // runs the full saveAndCleanup teardown — never bypass it with window.close(). + ipcMain.on('app:quit', () => { + app.quit() + }) + // Register the attach-listeners callback for hook-server spawned sessions setAttachListeners((id, session) => attachSessionListeners(id, session)) diff --git a/src/preload/index.ts b/src/preload/index.ts index 5f36cf8..4ba36bf 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -355,6 +355,10 @@ const api = { cleanupRemoveNotes: (): Promise => ipcRenderer.invoke('cleanup:removeNotes'), cleanupRemoveSessions: (): Promise => ipcRenderer.invoke('cleanup:removeSessions'), cleanupResetAppSettings: (): Promise => ipcRenderer.invoke('cleanup:resetAppSettings'), + + // App lifecycle — real quit via app.quit() so the full before-quit cleanup + // (session save, PTY teardown, MCP unregister, plugin uninstall) always runs + quitApp: (): void => ipcRenderer.send('app:quit'), } export type CleanupResult = { ok: boolean; error?: string } diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 2122a00..5e59707 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -970,11 +970,15 @@ export function App(): JSX.Element { return } - // Quit app — on Mac this is handled natively by Cmd+Q via the menu; - // on Windows/Linux there's no menu so we handle it here + // Quit app. This capture-phase handler fires before the macOS menu's + // quit accelerator, so it must perform a REAL quit: window.close() here + // would only close the window and leave the app lingering in the dock + // (macOS keeps apps alive on window-all-closed). app:quit → app.quit() + // runs the full before-quit cleanup (session save, PTY teardown, + // MCP unregister, plugin uninstall) on every platform. if (key === 'q') { e.preventDefault() - window.close() + window.api.quitApp() return } } diff --git a/src/renderer/components/FileExplorer.tsx b/src/renderer/components/FileExplorer.tsx index 48e2bbc..59ceee1 100644 --- a/src/renderer/components/FileExplorer.tsx +++ b/src/renderer/components/FileExplorer.tsx @@ -129,10 +129,14 @@ export function FileExplorer({ case 'ArrowLeft': case 'Backspace': e.preventDefault() - // Go up one directory + // Go up one directory, but only if the parent is actually accessible — + // navigating above an allowed root (e.g. /Users) returns an empty + // listing with no entries to move forward into, stranding the user. const parent = currentPath.split('/').slice(0, -1).join('/') if (parent) { - setCurrentPath(parent) + window.api.isDirectory(parent).then((ok) => { + if (ok) setCurrentPath(parent) + }) } break case 'Escape': @@ -156,8 +160,12 @@ export function FileExplorer({ transition={{ duration: 0.2 }} className="absolute inset-y-0 left-0 w-80 z-30 bg-zinc-900/95 backdrop-blur-xl border-r border-zinc-800 flex flex-col" > - {/* Header */} -
+ {/* Header — offset past the macOS traffic lights so the path isn't hidden behind them */} +
{currentPath} diff --git a/src/renderer/components/pipeline/PipelineView.tsx b/src/renderer/components/pipeline/PipelineView.tsx index da7e555..df25952 100644 --- a/src/renderer/components/pipeline/PipelineView.tsx +++ b/src/renderer/components/pipeline/PipelineView.tsx @@ -748,7 +748,7 @@ function SessionDrawer({ {stageMeta.label}

{card.title}

- +
@@ -907,7 +907,7 @@ function BacklogDetailDrawer({ Backlog

{todo.title}

- + {/* Meta: status + tags */} @@ -1299,7 +1299,10 @@ function ProjectPicker({ const select = (name: string | null): void => { onChange(name); setOpen(false) } return ( -
+ // titlebar-no-drag: the view-mode titlebars underneath keep their + // app-region: drag strip active through this overlay (drag regions are + // window-level and ignore z-order), and this trigger sits inside it. +
@@ -430,7 +434,7 @@ function ScheduleFormDrawer({ >

{task ? 'Edit schedule' : 'New schedule'}

- +
@@ -619,7 +623,7 @@ function RunTerminalDrawer({ task, run, onClose }: { task: ScheduledTask; run: S Run · {fmtTime(run.startedAt)}

{task.name}

- +