Finish, merge and deploy url://webshot/ and TinySite home-page thumbnails
Written 2026-10-17 ~02:40 UTC. RE-VERIFY: everything below is a write-time snapshot. Before acting, re-check each PR with gh pr view <n> --repo CodexCoder21Organization/<repo> --json state,mergeStateStatus,headRefOid,statusCheckRollup, each branch with git ls-remote https://github.com/CodexCoder21Organization/<repo>.git, and each version with curl -s -o /dev/null -w '%{http_code}' https://kotlin.directory/<group-path>/<artifact>/<version>/ (200 = published, 404 = not).
Mission
The owner asked on 2026-10-08: "The tinysite homepage has a list of recent sites, each site should include a preview/thumbnail generated by playwright or something. I think we have a screenshot service, right? Does the screenshot service support web screenshots? Should it support web screenshots or should that be a separate service?"
Decisions the owner made:
- Separate service.
url://webshot/, not an extension of url://screenshottest/ (which only compares golden screenshots of an uploaded WUI bundle and renders 127.0.0.1).
- One fresh DigitalOcean droplet per capture.
- "Build and deploy." The owner's authorization covers deploying
url://webshot/ and redeploying TinySite with the thumbnail pass enabled, for this effort only.
- Catalog. List TinySite in ServiceAtlas. Done in https://github.com/CodexCoder21Organization/ServiceAtlas/pull/54. Webshot still needs its own entry, added at deploy time.
Later the owner said "Drive this to completion." The session was paused on 2026-10-17 at the owner's request ("stop the delegates, create a handoff").
The binding design and contract are in https://github.com/CodexCoder21Organization/PlanRepository/blob/main/workstreams/Webshot.md, landed by https://github.com/CodexCoder21Organization/PlanRepository/pull/10763 and https://github.com/CodexCoder21Organization/PlanRepository/pull/10781. Read it first.
Architecture (what each repo is)
| Repo |
Role |
Coordinate |
| WebshotApi |
Contract: the WebshotApi interface, JSON models, limits and validation |
webshot.api:webshot-api |
| ScreenshotTestEmbedded |
Shared droplet and renderer code. Webshot uses its DropletManager (namePrefix webshot), recovery, and WorkerRecoveryOperation |
screenshottest.embedded:screenshottest-embedded |
| WebshotRunner |
CLI that runs on each worker droplet. It drives pinned Chromium as user webshot-browser inside a capture cgroup behind an nftables egress policy, and returns the PNG |
webshot.runner:webshot-runner |
| WebshotEmbedded |
The service implementation: queue, worker pool, droplet lifecycle, retention, hosted recovery |
webshot.embedded:webshot-embedded |
| WebshotServerService |
The process that hosts url://webshot/ on ContainerNursery |
webshot.server:webshot-server-service |
| TinySiteWui |
TinySite. Its background thumbnail pass calls url://webshot/ and stores .tinysite/thumbnail.png and .tinysite/thumbnail.json in each site's own Filedrop drop. /thumb serves them |
(service) |
Lockstep rule. The runner's request parser rejects unknown fields. The runner and WebshotEmbedded must therefore be built on compatible webshot-api versions; WebshotEmbedded checks this via RUNNER_WEBSHOT_API_VERSIONS and REQUEST_COMPATIBLE_WEBSHOT_API_VERSIONS. The target set is webshot-api 0.0.9 + screenshottest-embedded 0.0.19 + runner 0.0.4 + embedded 0.0.6 + server 0.0.3.
Merge order and gate
Merge in dependency order: api (done) → ScreenshotTestEmbedded → runner → embedded → server → TinySite.
Each PR needs all of the following before merging:
- A green required check on its final head.
- A clean adversarial review of that exact head by an independent reviewer. A review is void once the head moves.
- The merger's own final review.
CI change made in this effort. The shared kotlin.build (remote) check sat in_progress for hours with no build attached (recorded in https://github.com/CodexCoder21Organization/PlanRepository/blob/main/challenges/2026-10-16-2157-kotlin-build-remote-checks-stay-in-progress-with-no-build.md). Following the owner's 2026-10-16 "GitHub runner required, remote CI optional" rollout, every repo in this effort now requires the GitHub Actions job bld-build, which runs scripts/build.bash and scripts/test.bash with --local-and-remote and kompile-cli 0.0.124. kotlin.build (remote) is informational in these repos.
- WebshotApi, WebshotRunner, WebshotEmbedded, WebshotServerService: the existing rulesets (ids 24716086, 24716185, 24717264, 25275694) had
kotlin.build (remote) swapped for bld-build, strict.
- ScreenshotTestEmbedded: it had no ruleset. New ruleset 25286167 copies SimpleFileSystemServiceServer's: required
bld-build strict, merge queue (squash), PR required, no deletion, no force-push.
- Restoring the old rulesets: the pre-edit ruleset JSON was saved only in the session scratchpad. To restore, put
kotlin.build (remote) back as the required check (strict) in the four rulesets, and delete 25286167.
- Runner and embedded PRs: they do not yet carry the
bld-build workflow (next steps).
- Enqueueing: use the GraphQL
enqueuePullRequest mutation. Auto-merge is disabled in these repos.
Relevant PRs / refs
Merged
| PR |
What |
Merged |
| https://github.com/CodexCoder21Organization/WebshotApi/pull/1 |
Contract library, published 0.0.9 |
2026-10-17 02:28Z (head b33f82a) |
| https://github.com/CodexCoder21Organization/ScreenshotTestEmbedded/pull/10 |
WorkerRecoveryOperation (public cancellable recovery), 0.0.18. Merged into the PR 7 branch, not main |
02:06Z |
| https://github.com/CodexCoder21Organization/ScreenshotTestEmbedded/pull/11 |
bld-build workflow + kompile-cli 0.0.124 on main |
02:16Z |
| https://github.com/CodexCoder21Organization/PlanRepository/pull/10763 |
workstreams/Webshot.md design |
2026-10-08 |
| https://github.com/CodexCoder21Organization/PlanRepository/pull/10781 |
Contract table aligned with 0.0.7–0.0.9 and hosted recovery |
2026-10-17 02:00Z |
| https://github.com/CodexCoder21Organization/PlanRepository/pull/11031 |
Challenge: the url:// RPC transport drops exception types |
02:10Z |
| https://github.com/CodexCoder21Organization/PlanRepository/pull/11025, https://github.com/CodexCoder21Organization/PlanRepository/pull/11026, https://github.com/CodexCoder21Organization/PlanRepository/pull/11028 |
Challenge: kotlin.build remote checks stuck |
2026-10-16 |
| https://github.com/CodexCoder21Organization/ServiceAtlas/pull/54 |
TinySite catalog entry |
2026-10-08 |
| https://github.com/CodexCoder21Organization/TinySiteConventions/pull/2 |
0.0.3: .tinysite/ reserved, with THUMBNAIL_PATH, THUMBNAIL_STATE_PATH and isReservedSitePath |
2026-10 |
Open PRs and branches (every unmerged piece of work)
| Repo |
Branch |
Remote head |
PR |
What is on it |
State |
| ScreenshotTestEmbedded |
feat/name-prefix |
5fcfe4c1ba23d0e69d033400f17f71e9906c10e7 |
https://github.com/CodexCoder21Organization/ScreenshotTestEmbedded/pull/7 |
namePrefix support, per-prefix keys, recovery name filter, plus PR 10's WorkerRecoveryOperation, plus a merge of main ac944e9 |
bld-build green, 78/78. Both content parts were reviewed clean (PR 7 at 7b1304f; PR 10 at 8c176f1). The only post-review change is a conflict-free merge of main, which brought in the CI workflow. Ready to merge after a final look at the merge commit. Published 0.0.14–0.0.18 from this line |
| ScreenshotTestEmbedded |
fix/unknown-creation-request |
d56fa4202e3aab60975937dad55019fb4ddb0a22 |
https://github.com/CodexCoder21Organization/ScreenshotTestEmbedded/pull/12 |
A creation request the droplet service no longer knows is treated as settled (exact-name listing, then the record is removed). Published as 0.0.19 |
bld-build green, 80/80. Not reviewed yet. It targets main, so its diff includes PR 7's commits until PR 7 lands. It changed one existing test (testRestartUnknownWorkerRequest, "unknown" mode) to assert the new behaviour; the review must check that change |
| WebshotRunner |
webshot-runner-0.0.1 |
d5ba12de1c30d4dadfcfca6e7405174224f363f5 |
https://github.com/CodexCoder21Organization/WebshotRunner/pull/1 |
Runner 0.0.4 (published, built on api 0.0.9). Fixes three review findings: unbounded width, deep-tree stack overflow, empty truncated-PNG message. Adds cgroup memory.max 2 GiB, memory.swap.max 0 and memory.oom.group 1 |
37/37 local, and 37/37 as root on Ubuntu 24.04 (run). Needs a security re-review of 6b8d261..d5ba12d. No bld-build workflow yet, so the required check cannot run. PR description still describes 0.0.3 |
| WebshotRunner |
wip/runner-0.0.4 |
4f5f43fe65c6512313a2657f190ac07757d3d6d9 |
none |
WIP history of 0.0.4; same tree as d5ba12d |
Superseded by the PR branch; safe to delete |
| WebshotRunner |
wip/runner-0.0.4-final, probe/runner-0.0.4 (46c6aeb), probe/ubuntu-2404-worker-v2 (f252c75) |
d5ba12d / 46c6aeb / f252c75 |
none |
A duplicate of the PR head, plus real-host probe branches |
Throwaway; safe to delete |
| WebshotEmbedded |
webshot-embedded-0.0.1 |
402ad888f57c5ee9e2e0a1fa032a172bce12c1ff |
https://github.com/CodexCoder21Organization/WebshotEmbedded/pull/1 |
0.0.6 candidate (details below) |
0.0.6 not published. No full suite has run at this head; the last full run, at 6a86746, passed 32/34, and both failures were fixed afterwards. No bld-build workflow yet. PR description still describes 0.0.5. Not reviewed since 0.0.5 |
| WebshotEmbedded |
wip/webshot-embedded-testquality-g1, -g2, -g3 |
a4eaed6 / 00b1f86 / 7b5ca70 |
none |
Test-quality rewrites |
Already merged into the PR branch; safe to delete |
| WebshotServerService |
webshot-server-0.0.1 |
7b2830946f2fd43c8708b4b98199cd51abf7439f |
https://github.com/CodexCoder21Organization/WebshotServerService/pull/1 |
Host: binds first, recovers in the background with automatic retry (10 s doubling to 5 min), DEGRADED/UNHEALTHY health, error-code mapping, README route settings |
bld-build green. Last full run 18/18 at 2e4e01b; later commits' tests pass individually. Pins api 0.0.9, embedded 0.0.5, screenshottest-embedded 0.0.15; 0.0.3 not published. Review items 2, 3, 4 and 6 are done; 1 and 5 wait on embedded 0.0.6 (below) |
| TinySiteWui |
thumbnails |
0794f0b41e232946c34b88381af1719989922953 |
https://github.com/CodexCoder21Organization/TinySiteWui/pull/2 |
Thumbnail pass (rounds 1–4) |
bld-build green, 58/58. The round-4 review found three problems (below) |
| TinySiteWui |
wip/tinysite-thumbnails |
d035320a79ba1f90c5b387ffc64a3dcfbfe629b0 |
(two commits on top of PR 2's head) |
Round-5 fixes: 796e982 recognises Webshot answers through UrlResolver by their documented text; d035320 makes the kill-test bridge carry the remote error shape and corrects the README |
Full suite not run. It was stopped mid-run, so these commits are unverified and not on the PR branch. Pins are still api 0.0.7 / embedded 0.0.5 |
| TinySiteWui |
wip/tinysite-thumbnails-pre-rebase-ff59238 |
ff59238 |
none |
Pre-rebase snapshot |
Superseded; safe to delete |
| PlanRepository |
wip/webshot-orchestration-2026-10-16 |
396f5a01e5611bb4fabea8c5290e58c7a423bacd |
none |
Older orchestration briefs and ledger (wip/webshot/common.md, wip/webshot/ledger.md) |
Historical; this handoff supersedes the ledger |
Not pushed, on purpose: reviewer probe programs (kotlinc and javap probes, hostile-PNG generators, deep-tree generators) and the CI lane's pre-edit ruleset JSON. They lived only in the session scratchpad and are reproducible from the descriptions here.
Published artifacts (all byte-verified against local builds when published)
| Artifact |
Version(s) |
Note |
| webshot-api |
0.0.1–0.0.9 |
0.0.9 is current: jar c44569ec…, pom fff9c903… |
| screenshottest-embedded |
0.0.14–0.0.19 |
0.0.18 jar 2bdca2de…; 0.0.19 jar 4fde7134…, pom 984a68c8… (PR 12, unreviewed) |
| webshot-runner |
0.0.1–0.0.4 |
0.0.4 jar fbfe382b…, pom 4d7b4080… (built on api 0.0.9; unreviewed) |
| webshot-embedded |
0.0.1–0.0.5 |
0.0.5 jar 2fedfefc… (api 0.0.7, ste 0.0.15, default runner 0.0.3). 0.0.6 not published |
| webshot-server-service |
0.0.1, 0.0.2 |
0.0.3 not published |
Deployed
Nothing from this effort is deployed (verified 2026-10-17 ~02:45Z with container-nursery-cli:0.0.20 ... routes). There is no webshot route. The tinysite.wasmserver.com:443 route (danger_jarfile, /root/ContainerNursery/apps/tinysite-wui.jar) has no TINYSITE_THUMBNAIL_PASS variable and depends only on url://filedrop/. No lane deployed anything.
What was found and done
-
WebshotApi (merged).
- Reached 0.0.9 over nine review rounds. They tightened url rules (no userinfo, ports 1–65535, punycode, no zone id), strict JSON, Unicode Cc/Cf/Zl/Zp refusal, monotonic timestamps, a retention hard cap of 500, and an error cap of 2000 with
... [truncated].
- Added
RecoveryState, with FAILED getting its own refusal sentence.
- 0.0.8 restored the 0.0.6 binary signatures of
WorkerPoolStatus; 0.0.9 hides those overloads from Kotlin source with @Deprecated(level = HIDDEN).
- The README documents how errors look to remote callers (below).
-
The transport drops exception types — upstream, not fixed here. Through UrlResolver, every remote refusal reaches the caller as foundation.url.protocol.sandbox.SandboxException with the message Sandboxed code threw an exception: java.lang.InternalError: … UrlResolutionException: RPC request '<method>' failed: INVALID_PARAMS - <service message> cause: null. Callers must match on the documented message text. WebshotServerService tests/testRemoteClientErrorTypes.kts pins this. Recorded at https://github.com/CodexCoder21Organization/PlanRepository/blob/main/challenges/2026-10-17-0210-url-rpc-transport-drops-exception-types-every-remote.md, with the four code locations where the type is lost; the fix belongs in UrlProtocol/UrlResolver.
-
ScreenshotTestEmbedded.
- PR 7 adds namePrefix-scoped droplets.
- PR 10 adds
WorkerRecoveryOperation, so a host can cancel worker recovery and wait for it. Its docs were corrected three times until they matched the code exactly, including that only an interrupt observed by a covered call counts as cancellation.
- PR 12 fixes a recovery record that could never be resolved. It is an upstream fix: WebshotEmbedded cannot reach that record without depending on ScreenshotTestEmbedded internals.
-
WebshotRunner 0.0.3 → 0.0.4.
- 0.0.3 contains the browser in a per-capture cgroup (pids.max 2048, cgroup.kill, verified empty), uses descriptor-relative link-safe deletion, and decodes and scales PNGs row by row (peak about 8 MiB).
- The 0.0.3 security re-review found containment sound but raised three findings, all fixed in 0.0.4 and listed in the branch table. 0.0.4 is not yet re-reviewed.
- The reviewer also recommended
memory.max. On a real host this showed memory.swap.max 0 is needed, otherwise the browser swaps instead of being killed.
-
WebshotEmbedded 0.0.6 candidate (head 402ad88).
- Pins api 0.0.9 and screenshottest-embedded 0.0.19.
DEFAULT_RUNNER_COORDINATES is still runner 0.0.3; move it to 0.0.4 and add its row.
- Hosted recovery runs inside
WorkerRecoveryOperation. Failed teardown is retried in-process after 60 s, doubling up to 30 min.
shutdown() waits at most 60 s and returns captures still releasing a worker, which keep cleanupPending for the next start.
- A
close() versus recovery hang was found by test and fixed.
CaptureListener events, plus test-quality rewrites: no sleep-polling, no reading data-directory files.
- New refusal types in package
webshot.embedded: WebshotRefusalException (extends IllegalStateException), with subtypes CaptureQueueFullException, CaptureNotCompletedException, CaptureNotCancellableException, CaptureNotDeletableException, WebshotServiceClosedException and WebshotRecoveryPendingException.
- Faults use
WebshotServiceFault (a RuntimeException that is not an IllegalStateException; no paths in the message). Messages are byte-identical to the api 0.0.9 docs.
testUnknownCreationRequestRecovered passes on screenshottest-embedded 0.0.19. Nobody has confirmed it fails on 0.0.18.
-
WebshotServerService review (at 2e4e01b). Previous deploy blockers (crash-loop, image-slice type, bytecode encoding, URL-mode codes, unbounded listing) are fixed. Items from this review:
- Map only
WebshotRefusalException and IllegalArgumentException to INVALID_PARAMS; everything else becomes INTERNAL_ERROR with a logged stack trace. This waits on embedded 0.0.6. CancellationException pass-through is already done.
- Health becomes DEGRADED or UNHEALTHY. Done.
- README section on remote errors. Done.
- The backoff test is tied to the injected Clock. Done.
- Close latency with an in-flight deletion: re-check on embedded 0.0.6.
- README uses
--local. Done.
-
TinySite round 4 review (at 0794f0b). Round-3 fixes hold. Three problems remain:
- (1) Medium-high.
isWebshotRefusal (SiteThumbnails.kt ~837) does not recognise the remote SandboxException shape. A refused capture is then never counted as an attempt, so there is no backoff, a new intent and a createCapture call are written every pass, and in-flight slots fill up. The reviewer reproduced 4 refusals in 4 passes. All existing tests call WebshotEmbedded in-process, which hid this.
- (2) README.md:75 misstates when a refused write holds a site.
- (3)
requireCapturableBase checks syntax only, while Webshot also refuses hosts that resolve to non-public addresses; the README overclaims. The test's "good" example 203.0.113.9 is a documentation-range address.
Commits 796e982 and d035320 on wip/tinysite-thumbnails address all three but are unverified.
Next steps
- ScreenshotTestEmbedded PR 7. Look over the main-merge commit 5fcfe4c, confirm
bld-build is green on the head, then enqueue.
- ScreenshotTestEmbedded PR 12. Get an adversarial review of the fix and of the changed
testRestartUnknownWorkerRequest, then merge. It may need a rebase after PR 7 lands.
- WebshotRunner PR 1.
- Security re-review of 6b8d261..d5ba12d: width cap, iterative deletion and depth cap, cleanup Throwable handling, truncated-PNG messages and last IDAT CRC, memory.max, swap.max 0, oom.group.
- Add the
bld-build workflow. Copy it from https://github.com/CodexCoder21Organization/WebshotApi/commit/b33f82a with webshot.runner.buildMaven, and add TinySiteWui's Node and Playwright install step, because runner tests launch real Chromium. Bump kompile-cli to 0.0.124.
- Update the PR description for 0.0.4, get
bld-build green, then merge.
- WebshotEmbedded PR 1.
- Set the default runner to 0.0.4 and add its
RUNNER_WEBSHOT_API_VERSIONS row (api 0.0.9).
- Check that
testUnknownCreationRequestRecovered fails on screenshottest-embedded 0.0.18.
- Run the full suite with
--local.
- Add the
bld-build workflow (webshot.embedded.buildMaven; no Playwright needed).
- Publish 0.0.6 after a 404 check, byte-verified.
- Update the PR description, get an adversarial review of 0.0.5..head, then merge.
- WebshotServerService PR 1.
- Move to embedded 0.0.6 and screenshottest-embedded 0.0.19.
- Finish item 1 (match
WebshotRefusalException) with tests, and re-check item 5.
- Run the full suite, publish 0.0.3 byte-verified, and get
bld-build green.
- Get a re-review of 2e4e01b..head, then merge.
- TinySiteWui PR 2.
- Verify the two round-5 commits on
wip/tinysite-thumbnails: run the full suite with --local, then move them onto thumbnails.
- Move the pins to api 0.0.9 and embedded 0.0.6 together.
- Get a round-5 re-review that specifically checks classification under the real remote error shape, then merge.
- Separately,
testBrowserLeavesCommitAloneWhenResponseBodyIsLost is a known older flake (a cold Playwright install lock). Do not raise its timeout.
- Deploy (authorized by the owner's "build and deploy").
- Add the WebshotServerService fat jar to ContainerNursery as route
webshot (url://webshot/) using the CN CLI ≥ 0.0.20 add-route.
- Route settings: keepWarmSeconds -1, 128MB, dependency
url://digitalocean-droplets/, no JAVA_TOOL_OPTIONS. add-route has no flag for startupTimeoutSeconds (the README wants 120), so it must be set in the route config.
- Verify
health with details shows RECOVERED, then do one real capture of a public page.
- Redeploy TinySite from merged main with
TINYSITE_THUMBNAIL_PASS=enabled and the url://webshot/ dependency. Run git pull before building.
- Check https://tinysite.wasmserver.com shows thumbnails, and that its first screen has no errors.
- Add Webshot to ServiceAtlas, noting that access control is deferred (any peer can call every method).
- Housekeeping.
- buildtest run
d2c55190 (label WebshotServerService), created 2026-10-17 02:03Z, was PENDING with no droplet. It is most likely orphaned by a stopped --remote client. Do not archive or cancel buildtest runs by hand; report it if it is still stuck.
- Delete the throwaway branches listed above.
- Upstream follow-ups:
- FiledropApi should expose its per-drop limit. TinySite currently hard-codes 1 GiB, guarded by
testPerDropLimitMatchesFiledrop.
- The UrlProtocol/UrlResolver typed-error fix from the challenge record.
Reusable knowledge
- Clone setup. Private clones need
gh auth setup-git first, and commits need the git identity codexcoder21 / codexcoder21@gmail.com.
- Use
--local, not --remote. Pass --local to scripts/test.bash and scripts/build.bash when iterating. A killed --remote client can leave buildtest runs holding droplets.
- Byte-verify every publish. Check the version returns 404 before publishing, then download it back and compare SHA-256. Never reuse a version number.
- Changing only KDoc can still change the jar. Kotlin line-number tables move when lines shift inside a file with code. Keep line counts constant (reflow in place) and confirm with a hash comparison.
- Test remote error handling through the real transport, or with the exact
SandboxException message shape. In-process tests hide the type loss.
- A sweep found nothing deployed and no unpushed commits in any lane checkout. Verified with
git status, git log --branches --not --remotes and git ls-remote per repo at write time.