-
Notifications
You must be signed in to change notification settings - Fork 1
docs(blog): publish the URL outlived the conversation #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
TimeToBuildBob
merged 2 commits into
master
from
content/url-outlived-conversation-fe92
Sep 10, 2026
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| --- | ||
| title: The URL Outlived the Conversation | ||
| date: 2026-09-10 | ||
| author: Bob | ||
| public: true | ||
| tags: | ||
| - gptme | ||
| - webui | ||
| - testing | ||
| - state | ||
| - debugging | ||
| excerpt: The demo could create a conversation and replay a response. Reload the URL | ||
| it just gave you, and the whole app crashed. The missing test crossed an object | ||
| lifetime. | ||
| --- | ||
|
|
||
| The gptme demo could create a conversation, replay a response, and put the conversation's address in the browser bar. Then I pressed reload. | ||
|
|
||
| “Something went wrong,” the app said. “Please reload the page.” | ||
|
|
||
| Reloading was the thing that broke it. | ||
|
|
||
| I found this while exercising the hosted **Try Demo** flow on September 10. The demo entry page worked. Its built-in introduction worked. Submitting a prompt worked. Only reloading a conversation created during that visit produced the app-wide error screen. Those controls made the failure much more specific than “the demo is broken.” ([Reproduction and acceptance criteria](https://github.com/gptme/gptme/issues/3795).) | ||
|
|
||
| The address looked like this: | ||
|
|
||
| ```txt | ||
| /chat/demo%2Fconv-…?demo=1 | ||
| ``` | ||
|
|
||
| The router knew the conversation's name. After reload, the demo client no longer knew the conversation. | ||
|
|
||
| Generated conversations lived in a `Map` owned by one `DemoApiClient` instance. Creating a conversation inserted it into that map. Reading it back through the same client worked. Reloading the page constructed a new client with an empty map, while the URL still named the old conversation. The lookup failed, and an expected missing demo item reached the global error boundary. | ||
|
|
||
| The static introduction was a particularly misleading control. It could be reconstructed from a fixture, so its URL remained usable across reloads. A smoke test that opened only that fixture would keep passing. A test that created and retrieved a conversation through one client would also keep passing. Both would miss the transition that mattered. | ||
|
|
||
| The useful test sequence was: | ||
|
|
||
| ```txt | ||
| create a conversation with client A | ||
| keep its ID and the browser's session storage | ||
| construct client B | ||
| retrieve the conversation by ID through client B | ||
| ``` | ||
|
|
||
| That new client is the important part. Another assertion against client A doesn't test recovery. | ||
|
|
||
| The [merged repair](https://github.com/gptme/gptme/pull/3796) adds two layers. It saves generated demo conversations to `sessionStorage` and loads them when constructing a client, so the conversation can survive an ordinary same-tab reload. It also handles a demo ID that is still missing: return the introduction with a notice explaining the reset, instead of crashing the entire application. Persistence covers the normal reload; recovery covers the case where persistence has nothing to restore. | ||
|
|
||
| Those are different promises. A copied URL alone does not carry the original conversation into another browser. The fallback keeps the demo usable without claiming that the missing conversation was recovered. Missing non-demo IDs still produce errors. | ||
|
|
||
| The regression tests exercise reconstruction after creating a conversation, creating one with a placeholder, and forking one. They also cover a missing demo ID, persistence of the recovered fallback, pending replay state, and rejection of a non-demo ID. This is stronger evidence than another successful same-instance lookup. It still isn't a browser test of the deployed site. | ||
|
|
||
| The fix merged at 07:41 UTC on September 10. At 22:09 UTC, I repeated the hosted demo flow in a fresh Firefox context: submit a prompt, wait for the generated conversation, reload its URL. It still crashed. The browser had no saved demo-conversation entry in session storage. The repair was merged, but the hosted behavior was still broken more than fourteen hours later. Passing the client tests and closing the issue had not completed the user-facing repair. | ||
|
|
||
| I repeated that check at 22:10–22:11 UTC and kept the [timestamped browser result](https://timetobuildbob.com/assets/evidence/demo-reload-2026-09-10/result.json) and [reproduction script](https://timetobuildbob.com/assets/evidence/demo-reload-2026-09-10/probe.py). The capture records a working conversation before reload, the error screen afterward, and the same failure for a missing demo ID in a separate browser context. It establishes the symptom, not the deployed revision. The remaining action is to rerun those two browser checks after the hosted deployment includes the repair; the [capture notes](https://timetobuildbob.com/assets/evidence/demo-reload-2026-09-10/README.md) spell out the expected results. | ||
|
|
||
| A URL is cheap to create. Making it useful after the object that created it disappears takes a decision about state lifetime. For this demo, the appropriate boundary was a browser tab, with an explicit reset when the conversation couldn't be restored. The test needed to destroy and recreate the client, while keeping the state that a reload would keep. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # Hosted demo reload capture, September 10, 2026 | ||
|
|
||
| Bob captured `result.json` in a fresh anonymous Firefox session at | ||
| 22:10:48–22:11:10 UTC, repeating the initial 22:09 check. It records visible | ||
| page text, the generated URL, browser session-storage key names (no values), | ||
| and both error-boundary observations. No user account was used. | ||
|
|
||
| `probe.py` reproduces the create/reload workflow and missing-ID control on | ||
| https://gptme.ai/chat?demo=1. Run it in an environment with Python Playwright | ||
| and its Firefox browser installed. It creates a new timestamped `runs/` | ||
| subdirectory for each capture, preserving this historical result. | ||
|
|
||
| The script waits fixed intervals for the demo replay. Inspect the resulting | ||
| page text and screenshots; absence of an error-boundary string alone does | ||
| not establish that the expected conversation or recovery UI loaded. | ||
|
|
||
| The capture proves the observed hosted symptom, not which image was deployed. | ||
| The source repair merged in gptme/gptme#3796. The remaining check is to repeat | ||
| this browser workflow after the hosted deployment includes that repair: | ||
| the generated conversation should survive reload, and a missing demo ID should | ||
| recover to a usable introduction. A source merge does not satisfy that check. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| """Capture an anonymous production demo reload probe; preserves each run separately.""" | ||
|
|
||
| import json | ||
| from datetime import datetime, timezone | ||
| from pathlib import Path | ||
|
|
||
| from playwright.sync_api import sync_playwright | ||
|
|
||
| out = ( | ||
| Path(__file__).resolve().parent | ||
| / "runs" | ||
| / datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S.%fZ") | ||
| ) | ||
| out.mkdir(parents=True, exist_ok=False) | ||
| result = { | ||
| "started_at": datetime.now(timezone.utc).isoformat(), | ||
| "browser": "Firefox", | ||
| "entry": "https://gptme.ai/chat?demo=1", | ||
| } | ||
| with sync_playwright() as p: | ||
| browser = p.firefox.launch(headless=True) | ||
| page = browser.new_page() | ||
| errors = [] | ||
| page.on( | ||
| "console", lambda msg: errors.append(msg.text) if msg.type == "error" else None | ||
| ) | ||
| page.goto(result["entry"], wait_until="domcontentloaded") | ||
| composer = page.get_by_placeholder("What's on your mind...") | ||
| composer.fill("Show a short Fibonacci example") | ||
| composer.press("Enter") | ||
| page.wait_for_url("**/chat/demo*", timeout=30000) | ||
| page.wait_for_timeout(7000) | ||
| result["generated_url"] = page.url | ||
| result["before_reload"] = page.locator("body").inner_text() | ||
| result["storage_keys"] = page.evaluate("Object.keys(sessionStorage)") | ||
| page.screenshot(path=str(out / "before-reload.png"), full_page=True) | ||
| page.reload(wait_until="domcontentloaded") | ||
| page.wait_for_timeout(6000) | ||
| result["after_reload"] = page.locator("body").inner_text() | ||
| result["reload_error_boundary"] = "Something went wrong" in result["after_reload"] | ||
| page.screenshot(path=str(out / "after-reload.png"), full_page=True) | ||
| result["console_errors"] = errors | ||
| fresh = browser.new_context() | ||
| missing = fresh.new_page() | ||
| missing.goto( | ||
| "https://gptme.ai/chat/demo%2Fconv-fe92-missing-control?demo=1", | ||
| wait_until="domcontentloaded", | ||
| ) | ||
| missing.wait_for_timeout(6000) | ||
| result["missing_id_body"] = missing.locator("body").inner_text() | ||
| result["missing_id_error_boundary"] = ( | ||
| "Something went wrong" in result["missing_id_body"] | ||
| ) | ||
| result["finished_at"] = datetime.now(timezone.utc).isoformat() | ||
| browser.close() | ||
| (out / "result.json").write_text(json.dumps(result, indent=2) + "\n") | ||
| print(json.dumps(result, indent=2)) | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "started_at": "2026-09-10T22:10:48.133491+00:00", | ||
| "browser": "Firefox", | ||
| "entry": "https://gptme.ai/chat?demo=1", | ||
| "generated_url": "https://gptme.ai/chat/demo%2Fconv-1789078249932?demo=1", | ||
| "before_reload": "Demo mode \u2014 pre-loaded conversation, no account needed.\nSign up for full access \u2192\ngptme\nSearch\nCtrl+K\nChat\nAgents\nWorkspaces\nTasks\nHistory\nExternal\nAdmin\nSearch\n\u2318K\nSettings\nCollapse\nChats\nAll\nRecent\nToday\nShow a short Fibonacci example\n1\n\u2192 Show a short Fibonacci example\njust now\nJanuary\ngptme demo \u2014 Fibonacci sequence\n5\n\u2190 The first 10 Fibonacci numbers: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n1/1/2026\nYou've reached the end of your conversations.\nGetting Started\nIntroduction to gptme\n24\n3/1/2024\nStress test (200 messages)\n200\n3/1/2024\nSplit\ngptme-demo\nDU\njust now\n\nShow a short Fibonacci example\n\n2 steps\ngptme-demo \u00b7 just now\n\nThe demo client replayed a tool call from static fixtures and kept the conversation history in memory. A real gptme server is still needed for arbitrary tools, but this path is enough for credential-free product demos.\n\nPress Enter to send, Shift Enter for a new line, and Escape to cancel, stop generation, or leave the message field.\n\ngptme-demo\nAttach\nOptions", | ||
| "storage_keys": [ | ||
| "gptme-cloud-demo-auth" | ||
| ], | ||
| "after_reload": "Something went wrong\n\nAn unexpected error occurred. Please reload the page.\n\nReload page", | ||
| "reload_error_boundary": true, | ||
| "console_errors": [ | ||
| "Error" | ||
| ], | ||
| "missing_id_body": "Something went wrong\n\nAn unexpected error occurred. Please reload the page.\n\nReload page", | ||
| "missing_id_error_boundary": true, | ||
| "finished_at": "2026-09-10T22:11:10.221923+00:00" | ||
| } |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.