Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7cef702
Fix permission prompts and route parsing edge cases
theblazehen Jul 5, 2026
26a2b1e
Fix model defaults and chat restoration regressions
theblazehen Jul 6, 2026
35ddaab
Move permission titles to UI boundary
theblazehen Jul 6, 2026
6dbe7b1
Align chat follow-up queue with upstream
theblazehen Jul 6, 2026
9495680
Persist unsent chat composer state
theblazehen Jul 6, 2026
e0cba18
Recover unavailable restored chat attachments
theblazehen Jul 7, 2026
5dc6bb0
Migrate tabs to WorkspaceKey identity
theblazehen Jul 7, 2026
c4c7a7c
Spike global plus workspace policy
theblazehen Jul 7, 2026
bcce909
Drop ambiguous restored workspace tabs
theblazehen Jul 7, 2026
2cce18a
Close audit lifecycle and workspace tickets
theblazehen Jul 8, 2026
0c08a61
Preserve no-port server URLs
theblazehen Jul 8, 2026
835ca0c
Add pinned Home multi-server workspace flow
theblazehen Jul 8, 2026
0e434ff
Clean up detekt baseline nits
theblazehen Jul 8, 2026
ea09509
Document visual QA device blocker
theblazehen Jul 8, 2026
865d1e3
Complete Home architecture visual QA
theblazehen Jul 9, 2026
9f93066
Track UX review follow-ups
theblazehen Jul 9, 2026
bd1d108
Fix guide honesty and Home copy
theblazehen Jul 9, 2026
7238c5b
Restructure Home toward workspace launcher
theblazehen Jul 9, 2026
8cb7a1c
Establish scoped server identity contracts
theblazehen Jul 9, 2026
5d8557f
snapshot
theblazehen Jul 14, 2026
3e0bce7
snapshot
theblazehen Jul 17, 2026
679a715
Harden upstream workspace workflows
theblazehen Jul 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,9 @@ detekt-*.html
detekt-*.txt
repomix-*.xml
temp_research/
BUG_FIXING.md
msg.txt
local-adb-screenshots/
.opencode/package-lock.json
*.png
!screenshots/*.png
36 changes: 36 additions & 0 deletions .tickets/oa-07lr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
id: oa-07lr
status: closed
deps: [oa-6swf, oa-97i6, oa-nugm]
links: []
created: 2026-07-08T14:42:17Z
type: feature
priority: 1
assignee: Jasmin Le Roux
parent: oa-xju6
---
# Implement Home workspace detail drill-in

Clicking a workspace card in Home opens a workspace detail view inside Home rather than immediately creating a tab.

Workspace detail is the bridge between existing sessions and contextual Files/Terminal/Chat actions.

## Design

Workspace detail content:
- Workspace identity: server badge/name and full directory.
- Open work in this workspace: existing chat/files/terminal tabs with focus actions.
- Filtered sessions in this workspace using current SessionList behavior where possible.
- Recent workspace activity if available (recent files, terminal status), but do not create a notification feed.
- Small Start new here row: + Chat, + Files, + Terminal, Pin.

Back behavior: workspace detail -> Home top-level.

## Acceptance Criteria

- Workspace card click drills into workspace detail without creating a new tab.
- Existing chat/files/terminal tabs for that workspace are detected and focusable.
- Filtered sessions list supports search/open and retains necessary session actions (rename/share/delete/summarize/view changes) or links to a full filtered Sessions view.
- Start new here actions delegate to Start Work coordinator with target prefilled.
- Tests cover workspace click no-tab-creation, focus existing tab, and start new here target prefill.

19 changes: 19 additions & 0 deletions .tickets/oa-0e8o.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
id: oa-0e8o
status: closed
deps: []
links: []
created: 2026-05-07T14:48:05Z
type: bug
priority: 1
assignee: Jasmin Le Roux
tags: [files, editor, license, cleanup]
---
# Corrective batch for oa-hvd4 / oa-lmh0 reward hacks

Council audit (post-oa-3rk7) flagged reward hacks: 1) WorkspaceFileRepository.toDomain drops FileContentDto.hash so baseline conflict detection is dead in default API path; 2) OfishBaselineHasher digests in-memory String not on-disk bytes producing false 409s on CRLF/BOM files; 3) Termux GPL-3.0 libs lack conveying-source notice (relinking notice covers LGPL only); 4) MIT grammar LicenseEntries marked version=null which catalogue doc reserves for 'planned, not shipped'; 5) Stale FilesViewModel comment claims read DTO has no hash; 6) confirmSave Ok branch bumps contentGeneration unnecessarily wiping cursor/undo on save; 7) _themeTypeAnchor dead val.

## Acceptance Criteria

DTO.hash maps through WorkspaceFileRepository, OFISH baseline matches on-disk hash (or caveat is honest about scope), GPL notice present, MIT entries versioned, dead code removed, tests cover real adapter not fake-only.

26 changes: 26 additions & 0 deletions .tickets/oa-0f4m.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
id: oa-0f4m
status: closed
deps: []
links: []
created: 2026-05-01T17:44:25Z
type: task
priority: 1
assignee: Jasmin Le Roux
parent: oa-gt0g
tags: [design, workspace, sessions]
---
# Design lock C: optimistic mutation rollback contract

Commit 5 ports SessionListViewModel optimistic mutations as reducer intents w/ rollback. Underspecified: which mutations are optimistic (delete/rename/share/unshare/summarize?), what triggers rollback (HTTP error / timeout / SSE contradiction?), user-visible feedback (silent revert / snackbar / toast?), reducer shape (pending intent + confirm/reject events?), interaction with concurrent SSE events on the same entity.

## Acceptance Criteria

1) Per-mutation table: optimistic-or-not, local state transition, rollback trigger, user-visible feedback. 2) Reducer intent/confirm/reject shapes defined. 3) Concurrent SSE event semantics defined (server confirmation arrives before HTTP response — what wins?). 4) Stale-workspace mutation rejection behavior defined.


## Notes

**2026-05-01T18:19:06Z**

Decision locked. See docs/design-locks/C-mutation-on-failure.md
40 changes: 40 additions & 0 deletions .tickets/oa-0gah.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
id: oa-0gah
status: closed
deps: [oa-6swf, oa-7ipn, oa-ximf]
links: []
created: 2026-07-08T14:42:17Z
type: feature
priority: 1
assignee: Jasmin Le Roux
parent: oa-xju6
---
# Introduce multi-server connection registry

Replace/extend single active ConnectionManager assumptions with a registry capable of managing multiple server connections independently.

Flattened mixed-server tabs require Alpha chat, Beta files, and Local terminal to coexist without changing a global current server under the user's feet.

## Design

Candidate API:
ServerConnectionRegistry.connection(serverRef): StateFlow<ConnectionState>
ServerConnectionRegistry.api(serverRef): OpenCodeApi?
ServerConnectionRegistry.connect(serverId)
ServerConnectionRegistry.disconnect(serverId)
ServerConnectionRegistry.reconnectAll()

Each server owns its own OkHttp/auth client, API, SSE/event source, generation, reconnect policy, credential state, and coroutine scope.

Lifecycle policy:
- Servers with open tabs are kept/reconnected on foreground.
- Servers with no open tabs can lazy-connect when selected/opened.
- Auth failures stop retry storms and show badge/state.

## Acceptance Criteria

- Two saved servers can have independent connection states simultaneously.
- Failure/auth issue on one server does not block or overwrite another server's state.
- Existing single-server flows still work.
- Tests cover independent connect/disconnect/error states and foreground reconnect policy.

40 changes: 40 additions & 0 deletions .tickets/oa-0lb9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
id: oa-0lb9
status: closed
deps: []
links: []
created: 2026-05-10T09:55:10Z
type: task
priority: 2
assignee: Jasmin Le Roux
---
# Sweep stale OFISH sessions on startup or hydration

Problem:
OFISH creates hidden background sessions for probes/mutations and relies on finally cleanup. If Android kills the app mid-operation, those sessions can remain on the OpenCode server. The stale sweep implementation exists but is not called.

Evidence:
OfishSessionFactory.sweepStaleSessions(maxAgeMillis, limit) exists and filters OFISH-prefixed sessions, but repository search finds no production call sites. OFISH session names use the __ofish_ prefix. Capability/chunk/file operations create ephemeral OFISH sessions.

UX Constraint:
Users should not see server session lists polluted by old hidden OFISH sessions, and cleanup should not delete active or user-visible sessions. Cleanup failures should be logged/human-readable only where appropriate, not block normal app startup.

Expected Behavior:
The app periodically or opportunistically sweeps stale OFISH sessions for the active server/workspace lifecycle, such as on successful connection, hydration, or before first OFISH use.

Acceptance Criteria:
- Call sweepStaleSessions from an appropriate workspace/server-scoped lifecycle point.
- Use conservative max age and limit values to avoid deleting active probes.
- Ensure sweep runs at most once per connection/workspace interval to avoid network spam.
- Log sweep results and failures without surfacing raw protocol errors to users.
- Add tests for stale session selection if feasible.

Verification:
Run OFISH/session tests and ./gradlew :app:compileDebugKotlin. Manually create/leave a stale OFISH session and verify sweep removes it while preserving active sessions.


## Notes

**2026-05-10T11:16:18Z**

Wired stale OFISH session sweeping into OfishSessionFactory.withSession() before ephemeral session creation. Sweep is conservative: sessions must be at least 6 hours old, list is capped by existing default limit, active in-process OFISH sessions are skipped, and a process-wide per server/workspace guard runs at most once every 30 minutes. Sweep results/failures are logged only. Added unit coverage for once-per-workspace interval behavior. Verified with export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && ./gradlew :app:testDebugUnitTest --tests dev.blazelight.p4oc.data.files.ofish.OfishSessionFactoryTest and ./gradlew :app:compileDebugKotlin.
56 changes: 56 additions & 0 deletions .tickets/oa-0mel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
id: oa-0mel
status: closed
deps: []
links: [oa-dygk, oa-wmvc, oa-3yk2, oa-12ui]
created: 2026-07-05T18:06:47Z
type: bug
priority: 2
assignee: Jasmin Le Roux
parent: oa-nwha
---
# Resource tab and slash command display metadata

Problem:
Tab titles and slash command descriptions include user-facing hardcoded display metadata outside a dedicated resource/UI boundary. This overlaps but is not identical to command dispatch correctness.

Evidence:
Display-boundary audit identified TabBar.kt route/tab titles and ChatViewModel.kt/SlashCommandsPopup.kt built-in slash command descriptions as user-visible copy. oa-3yk2 owns command dispatch semantics; oa-dygk owns popup placement/metadata UX. This ticket owns resource-backed display text for tab/command metadata.

UX Constraint:
Tab and command metadata must remain compact enough for phone screens and must not take persistent space unnecessarily. Labels/descriptions should be clear, localized where possible, and consistent between typed slash suggestions and command palette.

Expected Behavior:
Tabs and slash commands expose stable ids/semantic types; UI maps them to resource-backed labels/descriptions. Server/custom command descriptions remain upstream-provided content and are not overwritten by Android hardcoded text unless classified as local built-ins.

Acceptance Criteria:
- Move Android local tab titles and local built-in command descriptions to resources or centralized UI formatter.
- Preserve upstream command descriptions for server/custom/MCP/skill commands.
- Coordinate with oa-3yk2 so only commands classified as local built-ins receive Android resource metadata.
- Coordinate with oa-dygk so popup display metadata remains consistent.
- Add tests for local built-in display metadata versus upstream command metadata preservation.

Verification:
Run targeted command metadata/popup/tab title tests and compile after implementation.


## Notes

**2026-07-06T08:57:34Z**

Workspace label source clarification from 2026-07-05 discussion:

TabBar.getTabTitle currently takes workspaceDirectory: String? and builds suffixes from it at TabBar.kt:209-234. After oa-e6g3, TabState should store WorkspaceKey? instead of raw String? workspaceDirectory, so this ticket's tab title/resource work must derive workspace labels from WorkspaceKey rather than nullable directory strings.

Expected label model after oa-e6g3:
- WorkspaceKey.Directory('/path/project-a') -> resource-backed suffix/display label such as 'project-a' and titles like 'Sessions · project-a'.
- WorkspaceKey.Global -> intentional top-level/server-wide tab. Product may choose either no suffix for default top-level views (e.g. 'Sessions') or a resource-backed 'Server-wide' suffix where clarity is needed.
- workspaceKey == null -> legacy/missing workspace recovery label, not the same as Global.
- WorkspaceKey.SessionScoped(sessionId) -> resolve to displayable workspace/session context before title formatting or show a recovery/loading label.

Dependency note:
The tab title source should switch cleanly after oa-e6g3's TabState.workspaceKey change. Until then, avoid further entrenching workspaceDirectory:String? in new title-formatting APIs.

**2026-07-07T20:26:14Z**

Implemented resource-backed tab and slash-command display metadata. Tab title formatting now takes TabTitleLabels populated with stringResource at the MainTabScreen/TabBar UI boundary, with compact tab workspace labels including dedicated tab_workspace_global. Built-in slash commands now carry no ViewModel hardcoded descriptions; builtInCommandDescriptionRes maps local built-ins to R.string.slash_command_* resources, and Compose boundaries resolve those descriptions for ChatInputBar slash filtering/suggestions and CommandPalette while preserving custom/MCP/skill/upstream descriptions. Slash suggestions now display the resolved description line consistently with the palette. Added CommandMetadataTest for built-in resource mapping, unknown fallback, and preservation of custom/MCP/skill/unknown-built-in descriptions; added TabBarTitleTest for route/workspace title cases. Verification: ./gradlew :app:testDebugUnitTest --tests dev.blazelight.p4oc.ui.components.command.CommandMetadataTest --tests dev.blazelight.p4oc.ui.tabs.TabBarTitleTest; ./gradlew :app:compileDebugKotlin.
41 changes: 41 additions & 0 deletions .tickets/oa-0p94.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
id: oa-0p94
status: closed
deps: []
links: []
created: 2026-05-10T13:33:04Z
type: task
priority: 2
assignee: Jasmin Le Roux
---
# Clean up Detekt unused-code baseline findings

Problem:
Detekt is now wired into Gradle with app/detekt-baseline.xml. The baseline includes confirmed unused-code-style findings that should be cleaned up deliberately instead of bulk-deleting ambiguous Android/framework/Compose/resource-referenced code.

Evidence:
app/detekt-baseline.xml currently contains NoUnusedImports entries for multiple files, UnusedParameter entries in AgentsConfigScreen.kt, SkillsScreen.kt, and StreamingMarkdown.kt, plus UnusedPrivateProperty entries for FilePickerManager.kt, MainTabScreen.kt, and MdnsDiscoveryManager.kt.

UX Constraint:
Dead-code cleanup must be safe and boring. Do not delete public/API/framework/Compose preview/serialization/reflection/resource-referenced declarations without verifying usage. Avoid UI or behavior changes unless the code is proven unused.

Expected Behavior:
Remove or suppress confirmed unused imports/parameters/private properties where safe, shrink the Detekt baseline accordingly, and keep ./gradlew :app:detekt passing.

Acceptance Criteria:
- Review the NoUnusedImports, UnusedParameter, and UnusedPrivateProperty IDs in app/detekt-baseline.xml.
- Remove confirmed unused imports and private declarations where safe.
- For unused parameters that are part of callbacks, overrides, Compose APIs, or expected external signatures, keep them and use an explicit suppression or rename strategy if needed.
- Regenerate or manually shrink app/detekt-baseline.xml only for fixed findings.
- Verify ./gradlew :app:detekt and ./gradlew :app:compileDebugKotlin.

Verification:
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && ./gradlew :app:detekt
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && ./gradlew :app:compileDebugKotlin


## Notes

**2026-05-10T14:25:44Z**

Partial cleanup applied: removed stale read-only toggle callbacks, markdown streaming pass-through parameter, FilePickerManager fileRepository constructor dependency, MainTabScreen focus/keyboard unused locals, and MdnsDiscoveryManager DEFAULT_SERVER_PORT. compileDebugKotlin passes. Detekt is not clean: after removing the baseline IDs it reports remaining NoUnusedImports plus unrelated pre-existing/baseline-churn style findings; broad detekt --auto-correct was attempted and should be reviewed separately before closing.
39 changes: 39 additions & 0 deletions .tickets/oa-0qgz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
id: oa-0qgz
status: closed
deps: [oa-s5jj]
links: []
created: 2026-05-05T18:19:53Z
type: task
priority: 2
assignee: Jasmin Le Roux
parent: oa-ssm2
tags: [files, ofish, benchmark]
---
# Empirical OFISH chunk-size benchmark + runtime probe

JVM benchmark script that drives OpenCodeApi.executeShellCommand against a running opencode serve to find the optimal chunk size empirically. No hard upper cap.

Run target: PENCODE_SERVER_PASSWORD=hunter2 opencode serve --hostname 0.0.0.0 --port 4096 (user: opencode).

Bench script (app/src/test/java/dev/blazelight/p4oc/bench/ChunkSizeBenchmark.kt):
1. Create ephemeral session.
2. Probe sizes geometrically: 64 KiB, 256 KiB, 1 MiB, 4 MiB, 16 MiB, 64 MiB, ... DOUBLE until first failure (no upper cap in algorithm).
3. For each size: emit base64 payload via heredoc-on-stdin OFISH command, sha256 verify, measure latency.
4. Pick highest-throughput SUCCESSFUL size as the constant.
5. Delete session, clean temp file.

Output: const val OFISH_DEFAULT_CHUNK_BYTES: Int = <bench result>.

Runtime probe at workspace connect (in OfishSessionProvider equivalent):
- Try one probe write at OFISH_DEFAULT_CHUNK_BYTES.
- If success: use constant.
- If fail: halve until success or 64 KiB minimum.
- NEVER probe upward — only the manual benchmark grows the constant.

Re-run benchmark when server, proxy, OkHttp, or deployment config changes.

## Acceptance Criteria

Benchmark script runs against the dev server in less than 60s. Outputs a chunk size with measured throughput. Constant committed to source. Runtime probe halves correctly on simulated server-rejection. No hard upper cap anywhere in the chunking code path.

32 changes: 32 additions & 0 deletions .tickets/oa-0r8n.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
id: oa-0r8n
status: closed
deps: []
links: []
created: 2026-05-10T09:51:33Z
type: bug
priority: 1
assignee: Jasmin Le Roux
---
# Raise OkHttp per-host limits for persistent SSE and PTY sockets

Problem:
The app uses persistent SSE and PTY WebSocket connections to the same host. OkHttp's default Dispatcher maxRequestsPerHost is 5, so several terminal tabs plus SSE can starve ordinary REST calls behind long-lived connections.

Evidence:
ConnectionManager builds REST/SSE/WebSocket clients from a shared base OkHttpClient/ConnectionPool but does not configure Dispatcher.maxRequestsPerHost. SSE holds a long-lived connection, and each terminal tab uses a WebSocket. OkHttp defaults can queue additional requests per host when the limit is reached.

UX Constraint:
Opening multiple terminal tabs must not make chat sends, project/session loading, file operations, or settings API calls hang indefinitely.

Expected Behavior:
ConnectionManager configures an OkHttp Dispatcher appropriate for multiple persistent connections per server, and all derived clients share or use a compatible dispatcher policy.

Acceptance Criteria:
- Set maxRequestsPerHost high enough for expected concurrent SSE/WebSocket/REST usage, with a documented rationale.
- Verify derived REST/SSE/WebSocket clients use the intended Dispatcher policy.
- Preserve connection pooling and auth behavior.
- Add a test or manual verification scenario with SSE + multiple terminals + REST request.

Verification:
Run ./gradlew :app:compileDebugKotlin. Manually open several terminal tabs and verify session/project/file REST calls still complete.
Loading
Loading