feat(workbench): adopt streamlit-style codespace onboarding (single-tab Simple Browser)#158
Merged
Merged
Conversation
…ab Simple Browser) After researching how high-quality public repos (streamlit/streamlit-example, microsoft/vscode-remote-try-*, n8n, vercel/next.js community templates) handle Codespaces buttons that should land users on a running web app, the dominant pattern is well-established and documented in GitHub's "Customizable Initial Experience" changelog. Our existing #155/#157 setup did the right thing but reached for non-idiomatic primitives. This PR aligns with the convergent best-practice recipe. ## What changes vs main 1. `portsAttributes."5173".onAutoForward`: `openBrowserOnce` → `openPreview`. The forwarded port now opens in VS Code's Simple Browser editor pane (single-tab UX, no separate browser tab, no popup blocker) instead of as a separate external tab. Identical to streamlit-example's port config. 2. `customizations.codespaces.openFiles: [".devcontainer/welcome.md"]` added. Codespaces natively opens these files as editor tabs on first launch — replaces the hand-rolled `code .devcontainer/welcome.md` call that the banner script was doing. 3. `postAttachCommand`: string form → named object form `{"workbench": "bash .devcontainer/workbench-banner.sh"}`. The terminal panel that hosts the command now gets the human label "workbench" instead of an opaque shell tab — matches streamlit's `{"server": "..."}` shape. 4. `postCreateCommand` → `updateContentCommand`. `postCreateCommand` fires once per codespace lifetime (on initial build). `updateContentCommand` fires whenever Codespaces detects content updates (e.g., the user pulls latest main), so deps refresh automatically after a git pull. Streamlit's recommended pattern. 5. `image`: `mcr.microsoft.com/devcontainers/python:3.12-bookworm` → `:1-3.12-bookworm`. Pins the major version of the devcontainer image so future devcontainer-image releases can't unexpectedly break the workbench. Standard practice across the microsoft/vscode-remote-try-* references. ## What's simplified - `workbench-banner.sh` no longer calls `code .devcontainer/welcome.md` (handled by `openFiles`) or `code --open-external <url>` (handled by `openPreview`). The script is now narrowly focused: print the URL banner, tail the three tier logs. - `postStart.sh` no longer calls `code --open-url "vscode://vscode.simpleBrowser.api.open/http://127.0.0.1:5173/"` — that was a workaround for the popup-blocker problem and it used the wrong (localhost) URL anyway. `openPreview` handles the Simple Browser open natively with the correct forwarded URL. ## Net user experience after this lands Click Launch workbench → **one** Firefox tab opens (VS Code Web). Inside: - welcome.md as an editor tab (via `openFiles`) - Simple Browser editor tab with the workbench rendered inline (via `openPreview`) - Terminal panel labeled "workbench" showing the 🚀 banner + tier logs Single window. No popup blocker. No permission prompt. Same recipe streamlit + the official GitHub Codespaces docs recommend. ## Refs - streamlit/streamlit-example .devcontainer/devcontainer.json — the canonical exemplar of this recipe. - GitHub Codespaces "Customizable Initial Experience" changelog (2022-10-26) — documents `openFiles` + `postAttachCommand` + `onAutoForward: openPreview` as the intended three-key pattern. - microsoft/vscode-remote-try-* — base image version pinning precedent (`1-3.12-bookworm` etc.). Signed-off-by: Federico Kamelhar <federico.kamelhar@oracle.com>
fede-kamel
added a commit
that referenced
this pull request
May 12, 2026
…ew alone (#160) PR #158 dropped the active `code --open-url` call from the banner script, betting that `portsAttributes.5173.onAutoForward: "openPreview"` in devcontainer.json would handle the workbench-tab open natively. That bet only pays off **the first time** the port binds — `openPreview` is a one-shot trigger that silently no-ops on every re-attach, every manual `bash .devcontainer/workbench-banner.sh`, every postStart restart. Users in those scenarios get a "Vite is ready, here's the URL" banner with no actual tab opening. Confirmed in a live codespace: `bash .devcontainer/workbench-banner.sh` prints the banner but no Simple Browser tab appears. ## Fix Add the active call back to the banner: code --open-url "vscode://vscode.simpleBrowser.api.open/${URL}" This deterministically opens the workbench in VS Code's Simple Browser editor pane every time the banner script runs — on postAttach, on manual re-run, on tier-crash recovery, etc. Same single-tab streamlit-pattern UX, but programmatically triggered instead of relying on the one-shot port-forward event. Layered defences (any one is sufficient; all four together cover every failure mode): 1. `customizations.codespaces.openFiles` opens welcome.md as an editor tab (always). 2. `portsAttributes.5173.onAutoForward: openPreview` opens Simple Browser on first port-bind (usually). 3. `code --open-url` in this script opens Simple Browser deterministically on every attach (now, after this PR). 4. The printed banner URL is a ⌘-clickable manual fallback. `code --open-url` is a no-op outside VS Code, so local devcontainer and Docker runs are unaffected — they continue to see only the printed URL. Signed-off-by: Federico Kamelhar <federico.kamelhar@oracle.com>
3 tasks
fede-kamel
added a commit
that referenced
this pull request
May 12, 2026
…pen fix) (#161) After four iterations (#155, #157, #158, #160), the Codespaces Simple Browser auto-open was still unreliable. The user's symptom: open a fresh codespace, VS Code Web boots, tiers come up, banner prints — but no Simple Browser tab pops with the workbench. Manual click on the URL is still required. ## Root cause `portsAttributes.5173.onAutoForward: "openPreview"` only fires for the user when a VS Code client is **attached at the moment the port binds**. In our setup, `postStartCommand` boots all three tiers *before* the user's VS Code Web has finished attaching to the container. Vite binds on :5173 → no client attached → openPreview silently no-ops. By the time the user shows up, the port-forward event is already past and Simple Browser never opens. This is the timing trap that streamlit-example sidesteps by putting the server itself in `postAttachCommand`, not in `postStartCommand`. With the server in postAttach, the port binds **after** the user is attached, so openPreview's event handler receives the bind and pops Simple Browser. The earlier PR #160 also tried to fix this with an active `code --open-url "vscode://vscode.simpleBrowser.api.open/<url>"` call, but that URI format isn't a valid VS Code Command URI — should be `vscode://command/simpleBrowser.show?<JSON-encoded args>`. ## Fix 1. **Move tier startup from `postStartCommand` into `postAttachCommand`.** Server boots after the user attaches; ports bind with a client present; openPreview reliably fires. 2. **Make tier startup idempotent.** `postAttachCommand` fires on every attach (not just the first), so the script checks whether each port is already listening before launching its tier. Second attach is a no-op for the tiers. 3. **Use the correct Command URI** for the active Simple Browser open: `vscode://command/simpleBrowser.show?<URL-encoded JSON array>`. Belt-and-braces; works even if openPreview's port-bind event misfires for any reason. ## Implementation - New `.devcontainer/workbench-attach.sh` — single entry point that does idempotent tier-boot + Vite-wait + Simple-Browser-open + banner + log-tail. Replaces both `postStart.sh` and `workbench-banner.sh` (deleted). - `devcontainer.json` — `postStartCommand` removed; `postAttachCommand` now points to the new script. ## Why postStartCommand is gone entirely There's nothing for it to do anymore. Container build runs `updateContentCommand` (deps install). User attaches → `postAttachCommand` runs. No work fits between those two events that wouldn't be either too-early (port-bind race) or never-needed. ## Reference - streamlit/streamlit-example .devcontainer/devcontainer.json — the canonical pattern with server-in-postAttach. - VS Code Command URI docs — correct `vscode://command/<id>?<args>` shape. Signed-off-by: Federico Kamelhar <federico.kamelhar@oracle.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Aligns the workbench's Codespaces setup with the convergent best-practice recipe used by
streamlit/streamlit-exampleand documented in GitHub's "Customizable Initial Experience" changelog (Oct 2022). Single-window UX; no popup blocker; no permission prompt; no second browser tab.Five changes
portsAttributes.5173.onAutoForward:openBrowserOnce→openPreview. Workbench renders inside VS Code's Simple Browser pane instead of opening a separate browser tab. Identical to streamlit-example.customizations.codespaces.openFiles: [".devcontainer/welcome.md"]added. Native Codespaces mechanism replaces the hand-rolledcode .devcontainer/welcome.mdcall from the banner script.postAttachCommand: string form → named object form{"workbench": "..."}. Terminal panel gets the human label "workbench" instead of an opaque shell name.postCreateCommand→updateContentCommand. Deps refresh automatically when the user pulls latest main inside the codespace. Streamlit's pattern.python:3.12-bookworm→python:1-3.12-bookworm. Pins the devcontainer-image major version (matches microsoft/vscode-remote-try-* convention).Simplifications
workbench-banner.shno longer callscodefor files or URLs — both flows are now native devcontainer features. The script is narrowly observability-focused: print URL, tail tier logs.postStart.shno longer manually invokes Simple Browser viacode --open-url— that'sopenPreview's job now. Also drops the brokenhttp://127.0.0.1:5173/URL (wrong host inside VS Code Web).Net user experience
Click Launch workbench → one browser tab opens (VS Code Web). Inside:
Single window. No second tab to hunt for, no popup blocker, no permission prompt.
Verification plan
Related
openFilesfor the welcome partopenPreviewReferences