Skip to content

feat(desktop): ports.json 定義ポートの常時表示#7

Merged
MocA-Love merged 1 commit intomainfrom
feat/desktop-ports-static-always-show
Mar 28, 2026
Merged

feat(desktop): ports.json 定義ポートの常時表示#7
MocA-Love merged 1 commit intomainfrom
feat/desktop-ports-static-always-show

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Mar 28, 2026

概要

ports.json に定義されたポートを、プロセス検出の有無にかかわらずサイドバーの Ports セクションに常時表示するように変更しました。

背景

現行の実装ではターミナルセッションの子プロセスツリーを lsof でスキャンしてリッスン中のポートを検出していますが、Docker Compose 等で起動されたポートは Docker Desktop のシステムプロセス(com.docker.backend / docker-proxy)がリッスンしているため、ターミナルの子プロセスとして検出できませんでした。

変更内容

  • EnrichedPort 型の拡張: detected フラグを追加し、検出情報(pid, processName, paneId 等)をオプショナルに
  • tRPC ports.getAll: 検出済みポートに加え、全ワークスペースの ports.json から未検出ポートも収集して返却
  • MergedPortBadge: 未検出ポートをグレー表示(bg-muted/40)、kill/open ボタンは非表示。ツールチップに "Not detected" を表示
  • WorkspacePortGroup: Close All は検出済みポートのみ対象
  • useKillPort: paneId が null のポートをスキップ

表示の違い

状態 表示
検出済み(従来通り) 青色バッジ、kill/open ボタンあり
未検出(ports.json 定義のみ) グレーバッジ、ラベル + ポート番号のみ

テスト計画

  • ports.json に複数ポートを定義し、プロセスが動いていなくても全ポートが表示されることを確認
  • Docker Compose で起動したポートが ports.json 経由で表示されることを確認
  • 実際にリッスン中のポートは従来通り青色バッジで表示されることを確認
  • 未検出ポートに kill/open ボタンが表示されないことを確認
  • フィルタトグル(ports.json のみ表示)が正しく動作することを確認

Summary by CodeRabbit

Release Notes

  • New Features
    • Ports defined in ports.json now always display in the sidebar, even when not detected by process monitoring
    • Un-detected ports are visually distinguished with grey styling; detected ports remain highlighted as active
    • "Close all ports" action now targets only active (detected) ports, leaving un-detected configured ports intact

Docker 等で起動されたポートはターミナルの子プロセスとして検出できないため、
ports.json に定義されたポートは未検出でもサイドバーに常時表示するように変更。
検出済みはアクティブ表示、未検出はグレー表示で視覚的に区別。
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 28, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 57d1cd58-a2a8-4dc6-9f90-46d21b62f351

📥 Commits

Reviewing files that changed from the base of the PR and between 621112d and 22a48e7.

📒 Files selected for processing (6)
  • README.md
  • apps/desktop/src/lib/trpc/routers/ports/ports.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/useKillPort.ts
  • apps/desktop/src/shared/types/ports.ts

📝 Walkthrough

Walkthrough

This pull request adds functionality to always display ports defined in ports.json within the sidebar, distinguishing between detected ports (shown as active) and un-detected ports from configuration (shown in a muted state). The backend now precomputes label caches and returns both detected and un-detected ports, while the frontend conditionally renders and styles them accordingly.

Changes

Cohort / File(s) Summary
Type Definition
apps/desktop/src/shared/types/ports.ts
Restructured EnrichedPort interface to add detected: boolean flag and make detection-related fields (pid, processName, paneId, detectedAt, address) nullable instead of required, enabling representation of ports without detected processes.
Backend Router Logic
apps/desktop/src/lib/trpc/routers/ports/ports.ts
Replaced lazy per-port label lookup with precomputed buildLabelCache() mapping; changed getAll to return enriched entries for all detected ports plus additional entries for un-detected static ports from configuration, each marked with appropriate detected status.
UI Components
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
Updated badge styling and visibility based on port.detected flag; conditionally render action buttons and process info tooltip only for detected ports; filter to detected ports only when closing all ports.
Hook Utility
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/useKillPort.ts
Added guards to prevent killing ports lacking a paneId; filters input ports to only those with non-null paneId before invoking termination.
Documentation
README.md
Added entry documenting the new ports.json constant display feature with distinction between detected and un-detected states.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Poem

🐰 Ports now always hop into view,
Whether dancing with processes or waiting for clues,
Detected ones shine bright and bold,
While quiet ones rest in grey, untold! 🎭

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-ports-static-always-show

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@MocA-Love MocA-Love self-assigned this Mar 28, 2026
@MocA-Love MocA-Love merged commit 30f273d into main Mar 28, 2026
1 check was pending
@MocA-Love MocA-Love deleted the feat/desktop-ports-static-always-show branch March 29, 2026 05:05
MocA-Love pushed a commit that referenced this pull request Apr 13, 2026
- Move 3 stray plan docs to repo-root `plans/` (V2 PR link design,
  normalize-github-query plan, voice agent plan)
- Move 3 shipped plan docs from `apps/desktop/docs/` into
  `apps/desktop/plans/done/`
- Mark Gap 7 resolved in V2_WORKSPACE_MODAL_GAPS.md (PR superset-sh#3356)
- Add AGENTS.md rule #7 documenting plan vs doc placement
MocA-Love pushed a commit that referenced this pull request Apr 13, 2026
- Move 3 stray plan docs to repo-root `plans/` (V2 PR link design,
  normalize-github-query plan, voice agent plan)
- Move 3 shipped plan docs from `apps/desktop/docs/` into
  `apps/desktop/plans/done/`
- Mark Gap 7 resolved in V2_WORKSPACE_MODAL_GAPS.md (PR superset-sh#3356)
- Add AGENTS.md rule #7 documenting plan vs doc placement
MocA-Love pushed a commit that referenced this pull request Apr 18, 2026
…uperset-sh#3517)

* remove 7 day rule

* Upgrade mastra

* upgrade ai

* Ad mastra

* refactor(desktop): remove dead provider-diagnostics plumbing

The provider-diagnostics store was fed by callSmallModel's per-attempt
reporting, which was removed when small-model tasks moved to direct AI-SDK
+ mastracode's AuthStorage. Nothing writes to the issue map anymore, so the
clearIssue mutation, getStatuses query, and diagnosticStatus plumbing in
ModelsSettings were all no-ops.

Settings still surfaces "Session expired / Reconnect" via auth-status alone.
ProviderIssue type collapsed from 8 codes to just "expired" to match.

* fix(auth): auto-refresh expired Anthropic OAuth tokens

Anthropic credentials were read via authStorage.get() everywhere, so
mastracode's built-in refresh flow never ran. Once the 1-hour access
token expired, status flipped to "Reconnect" and users had to do a
full PKCE re-auth, even though a valid refresh token was already
stored.

Resolvers now call authStorage.getApiKey() for oauth creds on expiry,
which triggers refreshToken() and persists the refreshed credential.
getAnthropicAuthStatus does the same before declaring issue: "expired".
Mirrors the pattern already used for OpenAI small-model auth.

* review: address PR feedback from cubic + coderabbit + greptile

- host-service ai-branch-name: run trailing-trim after slice so a
  100-char truncation can't re-introduce a bare "." or "-" that git
  rejects as an invalid ref (coderabbit / cubic #2, #7).
- host-service workspace-creation.generateBranchName: reuse the
  existing listBranchNames helper instead of the inline git walk,
  which classified off the short refname and could conflate a local
  "origin/foo" with refs/remotes/origin/foo (coderabbit #3).
- packages/chat shared/small-model: drop the unused
  hasSmallModelCredentials export; only a test mock consumed it
  (greptile #4).
- resolveAnthropicCredential: on refresh failure, return null instead
  of kind:"oauth" with a stale expiresAt so callers fall back cleanly
  (cubic #8).
- chat-service.getAnthropicAuthStatus: log context when refresh throws
  instead of silently swallowing (cubic #9).

* fix(chat): read auth.json directly instead of importing mastracode

Importing createAuthStorage from mastracode loads the entire CLI tree
(fastembed → onnxruntime-node's 208 MB native binary) via eager
top-level requires in mastracode's CJS entry. This crashed
electron-vite bundling and bloated the get-small-model chunk.

getSmallModel now reads mastracode's auth.json file directly using
the same path resolution logic (~/Library/Application Support/mastracode/
on macOS). Zero mastracode import, zero bundle impact. The chunk stays
at 1.2 MB (just @ai-sdk/anthropic + @ai-sdk/openai).

Production build verified: compile:app succeeds, Electron main process
boots with no onnxruntime error.

* docs(desktop): add manual testing plan for PR superset-sh#3517

* fix api key storage slot

* fix(auth): store API keys in dedicated slot so OAuth doesn't clobber them

setApiKeyForProvider and setStoredAnthropicApiKeyFromEnvVariables now
use authStorage.setStoredApiKey() (writes to "apikey:<provider>")
instead of authStorage.set() (writes to the main "<provider>" slot
shared with OAuth). This way connecting/disconnecting OAuth doesn't
overwrite or delete a stored API key.

resolveAuthMethodForProvider falls back to hasStoredApiKey() after
checking the main slot, so status correctly reports authenticated
when only an API key is stored.

* fix(auth): backup/restore API keys across OAuth connect/disconnect

mastracode's resolveModel only reads API keys from the main
authStorage slot (authStorage.get("anthropic")). OAuth login
overwrites this slot, and disconnect removes it — losing any
previously saved API key.

Fix: backup the API key to the dedicated apikey: slot before OAuth
connect, restore it after disconnect. setApiKeyForProvider now writes
to both slots (main for resolveModel compatibility, apikey: for
backup). resolveAuthMethodForProvider checks both.

Applies to both Anthropic and OpenAI providers.

* chore: add upstream PR reference to auth workaround

Point to mastra-ai/mastra#15483 so the backup/restore code can be
removed once upstream lands and we bump mastracode.

* refactor(desktop): derive settings provider action from status

Replace the cascade of if/else + canDisconnect flag with a single
getProviderAction(status) → connect | reconnect | logout | null.
Fixes "Active" badge + "Connect" button showing simultaneously
when authenticated via API key.

* fix(desktop): always show Logout when provider is active

Active providers now always show a Logout button. Clears OAuth or
API key depending on authMethod — no more "Active" badge with no
way to disconnect.

* fix(desktop): simplify OpenAI OAuth dialog + auto-open browser

Match Anthropic dialog's layout: remove the raw OAuth URL display
and "Tip" block, auto-open the browser on OAuth start. Change
"Back" to "Cancel" for consistency.

* refactor(desktop): unify OAuth dialogs into shared OAuthDialog

Extract shared OAuthDialog component with provider config object.
AnthropicOAuthDialog and OpenAIOAuthDialog become thin wrappers
that pass provider-specific labels and options.

* fix(desktop): show 'Copied!' feedback on Copy URL button

* refactor(desktop): merge provider account + API key into single card

Each provider section now renders AccountCard + ConfigRow inside
one rounded card with a divider, instead of two separate cards.
Removes the standalone "API Keys" collapsible section.

* refactor(desktop): compact OAuth row in provider settings card

OAuth row is now a single inline row (label + status + action)
instead of a stacked AccountCard. Both providers share the same
2-row card layout: OAuth row + API key row with divider.

* fix(desktop): contextual buttons in provider settings

Connect is now primary (filled). Save only shows when there's input.
Clear only shows when a key is saved. Removes visual noise from
empty-state provider cards.

* ui(desktop): add provider icons to settings section headers

* ui(desktop): show 'Not connected' badge instead of subtitle for disconnected providers

* ui: remove redundant disconnected subtitle

* ui: remove subtitle text from OAuth rows

* chore: remove dead AccountCard + getProviderSubtitle

* docs: update test plan to match current UI

* chore: move shipped plans to done/

---------

Co-authored-by: AviPeltz <aj.peltz@gmail.com>
MocA-Love pushed a commit that referenced this pull request Apr 18, 2026
…uperset-sh#3517)

* remove 7 day rule

* Upgrade mastra

* upgrade ai

* Ad mastra

* refactor(desktop): remove dead provider-diagnostics plumbing

The provider-diagnostics store was fed by callSmallModel's per-attempt
reporting, which was removed when small-model tasks moved to direct AI-SDK
+ mastracode's AuthStorage. Nothing writes to the issue map anymore, so the
clearIssue mutation, getStatuses query, and diagnosticStatus plumbing in
ModelsSettings were all no-ops.

Settings still surfaces "Session expired / Reconnect" via auth-status alone.
ProviderIssue type collapsed from 8 codes to just "expired" to match.

* fix(auth): auto-refresh expired Anthropic OAuth tokens

Anthropic credentials were read via authStorage.get() everywhere, so
mastracode's built-in refresh flow never ran. Once the 1-hour access
token expired, status flipped to "Reconnect" and users had to do a
full PKCE re-auth, even though a valid refresh token was already
stored.

Resolvers now call authStorage.getApiKey() for oauth creds on expiry,
which triggers refreshToken() and persists the refreshed credential.
getAnthropicAuthStatus does the same before declaring issue: "expired".
Mirrors the pattern already used for OpenAI small-model auth.

* review: address PR feedback from cubic + coderabbit + greptile

- host-service ai-branch-name: run trailing-trim after slice so a
  100-char truncation can't re-introduce a bare "." or "-" that git
  rejects as an invalid ref (coderabbit / cubic #2, #7).
- host-service workspace-creation.generateBranchName: reuse the
  existing listBranchNames helper instead of the inline git walk,
  which classified off the short refname and could conflate a local
  "origin/foo" with refs/remotes/origin/foo (coderabbit #3).
- packages/chat shared/small-model: drop the unused
  hasSmallModelCredentials export; only a test mock consumed it
  (greptile #4).
- resolveAnthropicCredential: on refresh failure, return null instead
  of kind:"oauth" with a stale expiresAt so callers fall back cleanly
  (cubic #8).
- chat-service.getAnthropicAuthStatus: log context when refresh throws
  instead of silently swallowing (cubic #9).

* fix(chat): read auth.json directly instead of importing mastracode

Importing createAuthStorage from mastracode loads the entire CLI tree
(fastembed → onnxruntime-node's 208 MB native binary) via eager
top-level requires in mastracode's CJS entry. This crashed
electron-vite bundling and bloated the get-small-model chunk.

getSmallModel now reads mastracode's auth.json file directly using
the same path resolution logic (~/Library/Application Support/mastracode/
on macOS). Zero mastracode import, zero bundle impact. The chunk stays
at 1.2 MB (just @ai-sdk/anthropic + @ai-sdk/openai).

Production build verified: compile:app succeeds, Electron main process
boots with no onnxruntime error.

* docs(desktop): add manual testing plan for PR superset-sh#3517

* fix api key storage slot

* fix(auth): store API keys in dedicated slot so OAuth doesn't clobber them

setApiKeyForProvider and setStoredAnthropicApiKeyFromEnvVariables now
use authStorage.setStoredApiKey() (writes to "apikey:<provider>")
instead of authStorage.set() (writes to the main "<provider>" slot
shared with OAuth). This way connecting/disconnecting OAuth doesn't
overwrite or delete a stored API key.

resolveAuthMethodForProvider falls back to hasStoredApiKey() after
checking the main slot, so status correctly reports authenticated
when only an API key is stored.

* fix(auth): backup/restore API keys across OAuth connect/disconnect

mastracode's resolveModel only reads API keys from the main
authStorage slot (authStorage.get("anthropic")). OAuth login
overwrites this slot, and disconnect removes it — losing any
previously saved API key.

Fix: backup the API key to the dedicated apikey: slot before OAuth
connect, restore it after disconnect. setApiKeyForProvider now writes
to both slots (main for resolveModel compatibility, apikey: for
backup). resolveAuthMethodForProvider checks both.

Applies to both Anthropic and OpenAI providers.

* chore: add upstream PR reference to auth workaround

Point to mastra-ai/mastra#15483 so the backup/restore code can be
removed once upstream lands and we bump mastracode.

* refactor(desktop): derive settings provider action from status

Replace the cascade of if/else + canDisconnect flag with a single
getProviderAction(status) → connect | reconnect | logout | null.
Fixes "Active" badge + "Connect" button showing simultaneously
when authenticated via API key.

* fix(desktop): always show Logout when provider is active

Active providers now always show a Logout button. Clears OAuth or
API key depending on authMethod — no more "Active" badge with no
way to disconnect.

* fix(desktop): simplify OpenAI OAuth dialog + auto-open browser

Match Anthropic dialog's layout: remove the raw OAuth URL display
and "Tip" block, auto-open the browser on OAuth start. Change
"Back" to "Cancel" for consistency.

* refactor(desktop): unify OAuth dialogs into shared OAuthDialog

Extract shared OAuthDialog component with provider config object.
AnthropicOAuthDialog and OpenAIOAuthDialog become thin wrappers
that pass provider-specific labels and options.

* fix(desktop): show 'Copied!' feedback on Copy URL button

* refactor(desktop): merge provider account + API key into single card

Each provider section now renders AccountCard + ConfigRow inside
one rounded card with a divider, instead of two separate cards.
Removes the standalone "API Keys" collapsible section.

* refactor(desktop): compact OAuth row in provider settings card

OAuth row is now a single inline row (label + status + action)
instead of a stacked AccountCard. Both providers share the same
2-row card layout: OAuth row + API key row with divider.

* fix(desktop): contextual buttons in provider settings

Connect is now primary (filled). Save only shows when there's input.
Clear only shows when a key is saved. Removes visual noise from
empty-state provider cards.

* ui(desktop): add provider icons to settings section headers

* ui(desktop): show 'Not connected' badge instead of subtitle for disconnected providers

* ui: remove redundant disconnected subtitle

* ui: remove subtitle text from OAuth rows

* chore: remove dead AccountCard + getProviderSubtitle

* docs: update test plan to match current UI

* chore: move shipped plans to done/

---------

Co-authored-by: AviPeltz <aj.peltz@gmail.com>
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.

1 participant