Repository navigation
2026 10 29 session uris
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: A browser's downloads are session://downloads[/{name}] and GET /browser/downloads. With no session open they say so instead of answering empty. workspace://files/downloads… is refused with its new spelling, and workspace://files no longer names a downloads folder. Console, network, current and everything else keep their workspace:// URIs.
Architecture: FOLDER_URI[DOWNLOADS] takes the new URI, so uri_of, describe (keep_with), show, the spec builder and every other reader of that table follow with no code of their own. parse_uri learns the one new prefix. names.retired_uri becomes an ordered table of moved prefixes. FILE_ROUTES holds whole paths, so the listing can live under /browser while staying the files surface's endpoint. Everything else is words.
Tech Stack: Python 3.11+, FastMCP 4, Starlette, pytest; Svelte 5 and vitest for one chip in ui/.
Spec: docs/superpowers/specs/2026-10-29-session-uris-design.md, the draft from #99, finished in this pull request. Rulings 1-5 and 12 are Dr K's. Rulings 6-11 and 13 are proposals awaiting him. This plan builds them as proposed. If he rules otherwise, the task that owns the ruling changes, and the Design lines below name which.
The spec is the design. This plan settles the following where the spec left the call to it:
-
The refusal table in
names.py, tried in order. A prefix matches the URI itself or the URI followed by/, and only the first match answers (ruling 11):MOVED = ( ("workspace://files/downloads", "session://downloads"), ("session://files/downloads", "session://downloads"), ("session://downloads", None), # served: nothing to refuse ("session://", "workspace://"), # the rename's ruling 5 )
session://matches by plain prefix, since it is a scheme. The refusal reads`{text}` is now `{new + rest}`, exactly as today.RETIRED_SCHEMEgoes. -
The no-session sentence is
NO_DOWNLOADSinhttp/files.py, raised asfaults.NoSession(NO_DOWNLOADS)(ruling 8):no session is open in this workspace; a download lives only as long as the browser that took it
It is raised by
folder(…, DOWNLOADS), byread_fileand bykeep, never bylisting_of, which the admin'ssectionscalls with an empty id and must keep answering[]. -
Without a
DATA_DIR,rootandfolderfor Files, Screenshots and Recordings raiseOFF, whether a browser is open or not: nothing they list comes from the browser any more.NOTHINGstays, forsectionsalone. -
folder's result for downloads losesbrowser, which would always be true.root's folders are[{name, uri, count}]for screenshots and recordings.downloads_countis deleted. -
FILE_ROUTESholds each path under the mount:FILE_ROUTES = { "list": ("get", "/files"), "screenshots": ("get", "/files/screenshots"), "downloads": ("get", "/browser/downloads"), "recordings": ("get", "/files/recordings"), "keep": ("put", "/files/{folder}/{name}/kept"), }
_routesregistersprefix + path. The listing's route name isbrowser_downloads._file_pathspublishes atf"{prefix}{template}"and tags an operationbrowserwhen its path starts with/browser,filesotherwise (ruling 9). -
The copy that changes, word for word:
-
SHAPES: "workspace://files/ for a file in Files, workspace://files/screenshots/ for a screenshot, workspace://files/recordings/ for a recording, or session://downloads/ for a download". - Files'
DESCRIPTION, first paragraph: "The files in Files: prints, and anything kept with keep_file. Also names two folders — workspace://files/screenshots and workspace://files/recordings — each with its own listing. The open browser's downloads are session://downloads." The second paragraph stays. -
FOLDER_DESCRIPTION[DOWNLOADS]gains a last sentence: "With no session open, reading it says so." -
keep_file's description: "uri is as workspace://files, its folders and session://downloads list it." -
show'sDESCRIPTION: "…a workspace://, session://, flow://, secret:// or skill:// URI…". -
read_resource's binary answer, for a URI undersession://downloads/: "Not returned: binary content is not text. Read session://downloads for this file's link, which any HTTP client can download." -
upload_file's docstring (mcp/tools.py), itsfileline: "file, by its uri: a file in workspace://files or its folders, or a download from session://downloads — without its bytes passing through you;". The refusal atcore/actions.py:995ends "…or file for any file this workspace has, by its workspace://files or session://downloads uri". The docstring atcore/actions.py:965says the same. - The handshake's
READING_POINTER: "…a URI — workspace://current, session://downloads, flow://flows, secret://secrets and the like, …". -
CAPTURE_KEPTinmcp/resources.py, appended toCONSOLE_DESCRIPTION,NETWORK_DESCRIPTIONand the end ofDESCRIPTION's capture paragraph:What was captured is the workspace's, not the browser's: it stays readable after the session ends, until capture.ttl passes with nothing new or open_session is called without capture.
- OpenAPI:
listFilesis summarised "Files' own listing, and its two folders." and described "Everything kept in Files, newest first, plus a count for workspace://files/screenshots and workspace://files/recordings — each its own listing, fetched separately so a caller who only wants to know whether there is anything to look at need not pay for either. The open browser's downloads are GET /browser/downloads."listDownloadsis described "Newest first, read from the browser itself. With no session open it is a 400 saying so: a download lives only as long as the browser that took it. A Grid failure from one that is open is a real fault and is not hidden."FileFolder's description is "One of the two folders named beside Files' own files.", itsnameenum["screenshots", "recordings"], and it has nobrowser.FolderListhas nobrowser, and its description is "One folder's own listing: workspace://files/screenshots, workspace://files/recordings or session://downloads."
-
-
The app's chip (ruling 13): in
ContextView.svelte, the chips block shows whendata.site_data || data.capture || data.live, and whiledata.liveit holds aDownloadschip with no pill. It is a button callingonshow('session://downloads')whenonshowis given, and otherwise the same disabled span the other chips use.
If Dr K rules otherwise: ruling 7 changes the two strings in FOLDER_URI/MOVED (Task 1, Task 2); ruling 8 the one constant (Task 2); ruling 9 one row of FILE_ROUTES (Task 2); ruling 10 root (Task 2); ruling 13 deletes Task 4's chip. Ruling 6 builds nothing.
- Python 3.11 is the floor: nothing 3.12+.
- No aliases (ruling 4). Nothing serves a moved URI or path; a moved URI is refused with its new spelling.
- Console, network,
workspace://current,flow://,secret://,skill://andui://keep their URIs (rulings 1, 5, 6). - The admin API's paths, the signed link paths (
/files/{session_id}/{name}),PUT /files/{folder}/{name}/keptand the admin's Files pane do not change (ruling 9). - Ruling 12's resource prefix is not built.
- Earlier specs and plans,
docs/saga/, and the shipped CHANGELOG sections are history and are not edited. - Every published shape that moves is a golden. Regenerate it with
GOLDEN_UPDATE=1 pytest tests/test_golden.py, read the diff, and commit the goldens on their own (CONTRIBUTING.md). - Wiki pages are regenerated with
python scripts/generate_wiki.py, andtests/test_wiki.pypasses. The wiki is thedocs/submodule: a commit inside it, then the pointer here. - No new integration flow (AGENTS.md: less is more).
- Never commit
openapi.yamlorkubed/selenium_flow/http/static/. - The checks, as CI runs them:
- Python (
test.yml):ruff check .andpytest. The integration tests need a Grid and run in CI. - OpenAPI (
quality.yml):python scripts/generate_openapi.pysucceeds. - UI, for Task 4:
npm --prefix ui ci && npm --prefix ui run build, thennpm --prefix ui test,npm --prefix ui run checkandnpm --prefix ui run lint. - Changelog (
pr.yml): a new line under## [Unreleased].
- Python (
-
A saved flow whose
upload_filestep namesworkspace://files/downloads/export.csv. A person expects the run to stop at that step saying "workspace://files/downloads/export.csvis nowsession://downloads/export.csv", not "is not a file". Task 2 tests it throughparse_uri. -
A flow from before 10-09 naming
session://files/downloads/export.csv. A person expects one fix, straight tosession://downloads/export.csv, not a hint toworkspace://files/downloads/…that is itself refused. Task 1 tests it. -
The admin's Files pane for a workspace with no browser. A person expects the empty Downloads section ("No browser — downloads go with it."), not an error across the whole pane, because the no-session refusal lives in
folder, notlisting_of. Task 2 testssectionswith no browser. -
workspace://fileswhile the Grid is down, with a browser recorded. A person expects their kept files listed: the root no longer asks the Grid anything. Task 2 tests it with a Grid that raises. -
Completing
session://downloads/{name}in VS Code with no session open. A person expects no suggestions, not an error dialog. Task 2 tests it.
Files:
- Modify:
kubed/selenium_flow/names.py:222-233 - Test:
tests/test_names.py
Interfaces:
-
Consumes: nothing.
-
Produces:
names.MOVED: tuple[tuple[str, str | None], ...], with every row of Design's table but the first;retired_uri(uri) -> None, unchanged in signature, raisingValueErrorper the table.RETIRED_SCHEMEno longer exists. Task 2 adds theworkspace://files/downloadsrow, in the same change that serves its new spelling, so the suite stays green between the two. -
Step 1: Write the failing tests. In
tests/test_names.py, keep the four tests there and add:
@pytest.mark.parametrize("old, new", [
("session://files/downloads/export.csv", "session://downloads/export.csv"), # Review Focus 2
("session://files/a.png", "workspace://files/a.png"),
("session://console?level=error", "workspace://console?level=error"),
])
def test_a_moved_uri_names_where_it_is_now(old, new): # ruling 11
with pytest.raises(ValueError) as exc:
names.retired_uri(old)
assert str(exc.value) == f"`{old}` is now `{new}`"
@pytest.mark.parametrize("uri", [
"session://downloads", "session://downloads/export.csv",
"workspace://files/downloads (1)", "workspace://files/screenshots/a.png",
])
def test_a_served_uri_passes(uri):
names.retired_uri(uri)- Step 2: Run them to see them fail
Run: pytest tests/test_names.py -v
Expected: FAIL. session://downloads is refused as workspace://downloads, and session://files/downloads/… is pointed at workspace://files/downloads/….
-
Step 3: Implement
MOVED, without its first row, andretired_uri, as Design has them. The comment aboveMOVEDcites the rename spec's ruling 5 and this spec's ruling 11. -
Step 4: Run the suite
Run: pytest && ruff check .
Expected: PASS. Nothing else reads a session://downloads URI yet.
- Step 5: Commit
feat: the refusal of moved URIs is a table, and session://downloads is not refused.
Files:
- Modify:
kubed/selenium_flow/names.py:MOVED's first row - Modify:
kubed/selenium_flow/http/files.py:FOLDER_URI(92-97),SHAPES(105-110),FILE_ROUTES(128-134),DESCRIPTION(152-159),parse_uri(181-191),downloads_count(328-334, deleted),root(337-389),folder(392-423),keep(558-564),read_file(614-618),FOLDER_DESCRIPTION(678-683),keep_file's description (816-827),_routes(840-934); a newNO_DOWNLOADSbesideOFF - Modify:
kubed/selenium_flow/mcp/completions.py:25,32 - Modify:
kubed/selenium_flow/mcp/show.py:FOLDERSandVIEWS(28-70),DESCRIPTION(143-148) - Modify:
kubed/selenium_flow/mcp/mirror.py:_binary(128-134) - Modify:
kubed/selenium_flow/spec/builder.py:_file_paths(768-823),_named_in_path's docstring (561) - Modify:
kubed/selenium_flow/spec/schemas.py:FileFolder,FolderList(724-769),_FILE_OPERATIONS(893-929) - Test:
tests/test_file_sections.py,tests/test_file_surfaces.py,tests/test_kept_files.py,tests/test_resources.py,tests/test_show.py,tests/test_show_inventory.py,tests/test_openapi.py,tests/test_wiki.py,tests/test_http_answer.py,tests/test_admin_files.py - Regenerate:
tests/golden/openapi.json; the wiki's generated pages
Interfaces:
-
Consumes:
names.MOVEDandnames.retired_uri(Task 1). -
Produces:
files.FOLDER_URI[DOWNLOADS] == "session://downloads";files.ITEM_URI[DOWNLOADS] == "session://downloads/{name}";files.NO_DOWNLOADS: str;files.FILE_ROUTESwith whole paths, as Design has it.files.downloads_countno longer exists. Task 4 readssession://downloadsas a literal. -
Step 1: Write the failing tests. In
tests/test_names.py,test_a_moved_uri_names_where_it_is_nowgains two rows:("workspace://files/downloads", "session://downloads")and("workspace://files/downloads/export.csv", "session://downloads/export.csv").In
tests/test_file_sections.py:-
test_a_uri_round_trips's downloads row becomes("downloads", "export.csv", "session://downloads/export.csv"). -
test_a_uri_that_names_no_file_is_refused_with_the_shapesloses"workspace://files/downloads"(it is now a refusal) and gains"session://downloads"and"session://downloads/a/b". -
test_the_root_lists_files_and_names_three_foldersbecomes…_names_two_folders, itsfoldersthe screenshots and recordings rows only. - Delete
test_with_no_browser_the_downloads_folder_says_soandtest_the_root_counts_downloads_without_describing_them. - Add, with this file's
Actions,FakeWorkspaces,store,SandTOKEN, importingre,requestsandkubed.selenium_flow.faults.NoSession:
-
def test_a_moved_download_uri_says_where_it_went(): # Review Focus 1
with pytest.raises(ValueError, match=re.escape("is now `session://downloads/export.csv`")):
files.parse_uri("workspace://files/downloads/export.csv")
def test_the_root_never_asks_the_grid(store): # ruling 10, Review Focus 4
class Down:
def files(self, _session_id):
raise requests.ConnectionError("grid down")
store.write_file(S, "report.pdf", b"p")
got = files.root(Actions(Down()), FakeWorkspaces(), store, TOKEN, S)
assert [f["name"] for f in got["files"]] == ["report.pdf"]
def test_without_a_data_dir_files_is_off_even_with_a_browser():
for read in (lambda: files.root(Actions(), FakeWorkspaces(), None, TOKEN, S),
lambda: files.folder(Actions(), FakeWorkspaces(), None, TOKEN, S, "screenshots")):
with pytest.raises(ValueError, match=re.escape(files.OFF)):
read()
def test_downloads_with_no_session_say_so(store): # rulings 3, 8
idle = FakeWorkspaces(browser="")
for call in (
lambda: files.folder(Actions(), idle, store, TOKEN, S, "downloads"),
lambda: files.read_file(Actions(), idle, store, "session://downloads/export.csv", S),
lambda: files.keep(Actions(), idle, store, "session://downloads/export.csv", S),
):
with pytest.raises(NoSession) as exc:
call()
assert str(exc.value) == files.NO_DOWNLOADS
def test_the_downloads_listing_has_no_browser_flag(store):
got = files.folder(Actions(), FakeWorkspaces(), store, TOKEN, S, "downloads")
assert got["uri"] == "session://downloads" and "browser" not in got
assert got["files"][0]["keep_with"].startswith('keep_file("session://downloads/')
def test_the_admins_sections_still_answer_with_no_browser(store): # Review Focus 3
got = files.sections(Actions(), FakeWorkspaces(), store, TOKEN, S, session_id="")
assert got["downloads"] == [] and got["browser"] is FalseEvery other literal workspace://files/downloads in these files becomes session://downloads (test_file_sections.py:229, 237, 254, 333; test_file_surfaces.py:48, 54, 122; test_resources.py:150, 160, 264; test_show.py:58, 61, 103, 141, 239, 415; test_show_inventory.py:38, 42, 110; ui fixtures are Task 4's). In test_file_sections.py:254, the expected message is files.NO_DOWNLOADS.
In tests/test_resources.py, beside test_a_person_picking_a_file_is_offered_their_own_files, with its reader:
DOWNLOAD = ResourceTemplateReference(type="ref/resource", uri="session://downloads/{name}")
async def test_download_names_complete_to_nothing_with_no_session(reader): # Review Focus 5
reader.workspaces.browser = lambda name: ""
async with Client(reader.mcp) as c:
offered = await c.complete(DOWNLOAD, {"name": "name", "value": ""})
assert offered.values == []
async def test_download_names_complete_from_the_open_browser(reader):
reader.workspaces.browser = lambda name: "abc"
reader.actions.grid.files = lambda session: [{"name": "export.csv", "size": 1}]
async with Client(reader.mcp) as c:
offered = await c.complete(DOWNLOAD, {"name": "name", "value": "exp"})
assert offered.values == ["export.csv"]In tests/test_show.py, add show on workspace://files/downloads/report.csv answering an error whose text contains "is now session://downloads/report.csv".
Over HTTP, in tests/test_kept_files.py:
- every route built from
FILE_ROUTESasf"/files{template}"(355-357, 404-406, 771, 810) becomestemplate; - add
test_the_downloads_listing_is_under_browser: with a live browser andGrid.filespatched toDOWNLOADS,GET /browser/downloadsanswers 200 withbody["uri"] == "session://downloads"; with no browser it answers 400 with{"error": files.NO_DOWNLOADS};GET /files/downloadsis not 200. -
PUT /files/downloads/report.pdf/kept(421, 439, 455) is unchanged, and still passes.
In tests/test_openapi.py:
def test_the_downloads_listing_is_the_sessions(spec): # ruling 9
operation = spec["paths"]["/browser/downloads"]["get"]
assert operation["x-mcp-resource"] == "session://downloads"
assert operation["tags"] == ["browser"]
assert "/files/downloads" not in spec["paths"]
assert "/files/{folder}/{name}/kept" in spec["paths"]In tests/test_wiki.py:139-150, the /browser row gains "list_downloads", and the /files row is set(files_module.FILE_ENDPOINTS) - {"downloads"}.
- Step 2: Run them to see them fail
Run: pytest tests/test_file_sections.py tests/test_file_surfaces.py tests/test_kept_files.py tests/test_resources.py tests/test_show.py tests/test_show_inventory.py tests/test_openapi.py -v
Expected: FAIL. The URIs, the route and NO_DOWNLOADS do not exist yet.
-
Step 3: Implement
MOVED's first row, thenhttp/files.pyas Design has it.parse_urichecksFOLDER_URI[DOWNLOADS] + "/"first, thenworkspace://files/, where a two-part path is a folder only forscreenshotsandrecordings.folderraisesNoSession(NO_DOWNLOADS)for downloads whenworkspaces.browser(name)is empty, before theNOTHING/OFFchecks, which now apply to the other three folders only.keepandread_fileraise the same in place of their two sentences. The module docstring's paragraph on listings says Downloads with no browser is refused, and the reserved-names paragraph says aworkspace://files/downloadsURI is refused namingsession://downloads.Then
completions.py:DOWNLOAD_NAMES = ("session://downloads", "files", "name")and the template key("session://downloads/{name}", "name").show.py:FOLDERS = ("screenshots", "recordings")for theworkspace://filesrows, and two rows forfiles.FOLDER_URI[DOWNLOADS]and its item (componentfolder, andfilewithentry=True), placed before theflow://rows._entryalready reads the listing at the URI's parent, so it needs nothing.mirror.py,builder.pyandschemas.pyas Design has them. -
Step 4: Regenerate and read the diffs
Run: GOLDEN_UPDATE=1 pytest tests/test_golden.py && python scripts/generate_wiki.py && pytest && ruff check . && python scripts/generate_openapi.py
Expected: PASS. The golden diff shows /files/downloads gone, /browser/downloads with the browser tag and x-mcp-resource: session://downloads, the two descriptions, and FileFolder/FolderList without browser. tools-on.json and tools-off.json show only keep_file's and show's descriptions. In docs/, list_downloads.md, list_files.md, keep_file.md, Actions.md and upload_file.md change, and nothing else.
Then: grep -rn "files/downloads" kubed tests --include=*.py
Expected: only the refusal's own table and the tests that check it, the admin paths (/admin/workspaces/{key}/files/downloads and its tests), PUT /files/downloads/…/kept and test_http_answer.py:40.
- Step 5: Commit the code, then the golden on its own, then the wiki pages inside
docs/and the pointer:feat!: a browser's downloads are session://downloads and GET /browser/downloads.
Files:
- Modify:
kubed/selenium_flow/mcp/resources.py:DESCRIPTION(19-34),CONSOLE_DESCRIPTION(117-125),NETWORK_DESCRIPTION(127-137), a newCAPTURE_KEPT - Modify:
kubed/selenium_flow/mcp/tools.py:READING_POINTER(206-210),upload_file's docstring (643-645) - Modify:
kubed/selenium_flow/core/actions.py:upload_file's docstring (965-971) and refusal (995-999) - Modify:
skills/selenium-flow/SKILL.md(164, 170-171);skills/selenium-flow/references/WORKSPACES.md(141-145) - Modify:
README.md(193, 197, 198) - Modify:
AGENTS.md: the vocabulary list (25-31), and thesession://paragraph under "Everything to read is a resource" (852-854) - Modify, inside
docs/:Files.md(6-10, 42, 78-83, 98),Workspaces.md(71-72),Deployment.md(90),_Sidebar.md(56) - Modify:
CHANGELOG.md(## [Unreleased]) - Test:
tests/test_resources.py,tests/test_capture_resources.py,tests/test_skill.py,tests/test_readme.py,tests/test_wiki.py - Regenerate:
tests/golden/tools-on.jsonandtools-off.json(upload_file),openapi.json(upload_file,currentWorkspace); the wiki's generated pages
Interfaces:
-
Consumes: the URIs and paths of Task 2.
-
Produces:
resources.CAPTURE_KEPT: str. -
Step 1: Write the failing test. In
tests/test_capture_resources.py:
async def test_the_capture_listings_say_they_outlive_the_browser(server): # ruling 1
async with Client(server.mcp) as c:
listed = {str(r.uri): r.description for r in await c.list_resources()}
for uri in ("workspace://console", "workspace://network", "workspace://current"):
assert resources.CAPTURE_KEPT in listed[uri], uriusing that file's server fixture, and importing resources from kubed.selenium_flow.mcp.
- Step 2: Run it to see it fail
Run: pytest tests/test_capture_resources.py -v -k outlive
Expected: FAIL, CAPTURE_KEPT cannot be imported.
-
Step 3: Write the content.
- The Python copy as Design has it.
-
SKILL.md: line 164 reads "Files' kept files, each with a link, and the two folders below". Lines 170-171 move below the recordings rows as
| `session://downloads` | the open browser's downloads; with no session open it says so |and| `session://downloads/{name}` | one of those |. -
WORKSPACES.md:141: "What is gone is the session's downloads,
session://downloads." -
README: 193 "…plus the two folders below"; 197
| a resource | `session://downloads`, `session://downloads/{name}` — the open browser's downloads |; 198 ends`GET /files/recordings`, `GET /browser/downloads`. -
AGENTS.md, vocabulary, after the session bullet:
session://names what dies with the session's browser: today only its downloads,session://downloads. Everything the workspace keeps, captured console and network included, isworkspace://(spec 2026-10-29-session-uris). -
AGENTS.md:852-854: A
session://URI from before the rename — in a saved flow'supload_filestep, say — is refused with itsworkspace://spelling, andworkspace://files/downloadswithsession://downloads, by every tool that takes a URI (spec 2026-10-29-session-uris, ruling 11).session://downloadsis the onesession://URI served. Saved flows are not rewritten; the error names the fix. -
Files.md: 6-10 name two folders, then "The open browser's downloads are
session://downloads." 42 reads "fromworkspace://files, its folders orsession://downloads". 78-79 become the twosession://downloadsrows. 81-83: "The listings are mirrored as JSON over HTTP:GET /files,GET /files/screenshots,GET /files/recordingsandGET /browser/downloads;PUT /files/{folder}/{name}/keptkeeps one, fromscreenshots,recordingsordownloads." 98: "workspace://files,session://downloadsand the admin UI hand these out…". -
Workspaces.md:71-72: "A
session://URI from before the rename is refused with itsworkspace://spelling, andworkspace://files/downloadswithsession://downloads, so a saved flow that names one says how to fix it.session://downloads, the open browser's downloads, is the onesession://URI served." -
Deployment.md:90 and _Sidebar.md:56:
session://downloads. -
CHANGELOG, the spec's line, word for word:
- **BREAKING:** a browser's downloads are now `session://downloads` (and `GET /browser/downloads`); `workspace://files/downloads` is refused with the new spelling.
-
Step 4: Check nothing was missed
Run: grep -rn "files/downloads" README.md AGENTS.md skills kubed scripts docs --exclude-dir=superpowers --exclude-dir=saga
Expected: only the admin paths (Administration.md:144, http/admin/, WorkspaceDetail.svelte), PUT /files/{folder}/{name}/kept's mentions, the signed-link sentence in Files.md, the refusal table, and the sentences above that name the refusal.
- Step 5: Regenerate, and run every check
Run: GOLDEN_UPDATE=1 pytest tests/test_golden.py && python scripts/generate_wiki.py && pytest && ruff check . && python scripts/generate_openapi.py
Expected: PASS. The goldens and the generated pages change only in the sentences above (current_workspace.md, upload_file.md).
- Step 6: Commit the code and the skill, the goldens on their own, the wiki pages inside
docs/with the pointer, thendocs: session://downloads, and what capture keeps, thenchore: changelog for session://downloads.
Files:
- Modify:
ui/src/lib/views/ContextView.svelte(30-56) - Modify:
ui/src/lib/types.ts(253, 268):browseroffFolderRefandFolderData - Test:
ui/src/lib/views/ContextView.test.ts,ui/src/lib/views/FilesView.test.ts(7-9),ui/src/App.test.ts(88)
Interfaces:
-
Consumes:
session://downloads(Task 2), as a literal. -
Step 1: Write the failing tests. In
ContextView.test.ts, with itsbase:
test('an open session offers its downloads', async () => {
const onshow = vi.fn()
const { getByRole } = render(ContextView, { props: { data: { ...base, live: true }, onshow } })
await fireEvent.click(getByRole('button', { name: 'Downloads' }))
expect(onshow).toHaveBeenCalledWith('session://downloads')
})
test('an idle workspace offers no downloads', () => {
const { queryByText } = render(ContextView, { props: { data: { ...base, live: false }, onshow: vi.fn() } })
expect(queryByText('Downloads')).toBeNull()
})FilesView.test.ts's fixture loses its downloads folder, and its expectations their downloads chip. App.test.ts:88 reads ['folder', { workspace: 's', folder: 'downloads', uri: 'session://downloads', count: 0, files: [] }, 'Downloads'].
- Step 2: Run them to see them fail
Run: npm --prefix ui ci && npm --prefix ui test
Expected: FAIL, no Downloads button.
-
Step 3: Implement the chip as Design has it, and drop
browserfrom the two types. -
Step 4: Run every UI check
Run: npm --prefix ui run build && npm --prefix ui test && npm --prefix ui run check && npm --prefix ui run lint && pytest tests/test_show_inventory.py
Expected: PASS, with no svelte-check warnings.
- Step 5: Commit
feat(ui): the app opens a session's downloads from its context card.
The action pages are generated from openapi.yaml, which is itself generated from the live MCP tool schemas — so they describe the server that shipped, not the one someone remembered. Prose belongs in wiki-notes/<tool>.md in the repo.
selenium-flow · MIT
Start here
Guides
Lifecycle
Going places
Doing things
Getting things out
Site data
Console and network