Conversation
TaskManager exported its success method as task.setSucceded, missing an e, while the JSDoc for that same function documented the correct spelling. Calling the documented name threw, and two callers already carried "(sic)" comments explaining the trap rather than fixing it. Renamed to setSucceeded and updated every call site. No alias is kept: the extensions were audited and none use the task API, so there is nothing left to break, and leaving the misspelling exported would only invite the next caller to trip on the same mismatch. The "(sic)" comments in the Python and PHP installers are removed too, since they now describe something that is no longer true.
Three problems with the first cut. The menu entry read "Migrate My Data…", which says nothing about where the data comes from. It now reads "Migrate My Data From phcode.dev…", with the domain derived from the configured legacy origin rather than written out separately so the label cannot drift from the host actually in use. The same derivation replaces the hardcoded domain in every other user facing string. Progress was an undismissable modal that blocked the editor for the whole transfer. At roughly 26ms per file that is minutes on a real project, which is not something to hold the app hostage for. The user is asked once, before anything is copied, the transfer then runs against a status bar task while they keep working, and a dialog appears again only at the end offering a reload. Files are streamed individually instead of zipped per folder. Measured on 300 files the zip was not earning its place: it produced an archive slightly larger than the input because JSZip stores uncompressed, so it only added a read/encode/decode pass on top of the IndexedDB cost that dominates either way. Streaming takes that sample from 8.0s to 5.8s, against a 3.2s floor for writing the same files locally at all, and it gives an exact per file progress count while capping memory at one file rather than one folder. The helper page no longer needs JSZip. Sunset date moves to 2026-09-10.
|
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.



Why
Browser storage is per-origin. A user landing on
web.phcode.devsees an empty editor while their projects, preferences, theme and extensions all sit on the old origin, invisible to the new one. This adds a one-time migration so the move does not cost them their data.How it works
web.phcode.dev, a hidden same-site iframe pointed at<legacy>/migrateAssist.htmlreports whether anything is worth moving. If there is nothing, absolutely nothing happens — no dialog, no interruption, normal boot. If there is, the user is asked once, the transfer runs in the background against a status-bar task while they keep working, and a dialog appears again at the end offering a reload.Moving: everything under
/fs/local/, the installed extensions and themes under/fs/app/extensions/,/fs/app/phcode.json, plus twoPhStorevalues (extensions.disabled,STATE_recentProjects).The load-bearing assumption
Browsers partition third-party storage by site (eTLD+1), not origin.
phcode.dev,staging.phcode.devandweb.phcode.devare all the same site, so the helper frame is same-site and reads real unpartitioned IndexedDB. Had the new home been on a different registrable domain this approach would have silently reported "nothing to migrate" everywhere rather than failing loudly.Security
migrateAssist.htmlhands the user's entire browser filesystem to whoever embeds it, so the origin check is the only gate:https://web.phcode.dev.evil.examplemust not pass.event.originre-validated on every message, not just the handshake.parentOriginquery param validated before anything is posted."*"./mntand..traversal refused.All of these are covered by tests.
Performance
Files stream individually rather than being zipped per folder. Measured on 300 files, the zip was not earning its place — it produced an archive larger than the input, because JSZip stores uncompressed, so it only added a read/encode/decode pass on top of the IndexedDB cost that dominates either way:
Writing those same 300 files locally, with no migration involved at all, costs 3167 ms — so IndexedDB is the floor and this now runs at ~1.75× it. Streaming also gives an exact per-file progress count and caps memory at one file instead of one folder.
Scope
Out of scope by design:
/mntFile System Access mounts (handles are origin-bound and unrecoverable — the files on disk are untouched, the user re-picks the folder), AI chat history (AI is not available in the browser app), and Safari/iOS, which is a product decision rather than a technical limit.Android/ChromeOS TWA users are sent to the Play Store rather than the new site, since the shipped APK only trusts
phcode.devand navigating it elsewhere surfaces a browser URL bar inside what looks like an app. "Stay here" is a genuine option there — managed school fleets can have the Play Store blocked entirely.Also in here
fix: correct setSucceded typo in the TaskManager task API— TaskManager exported its success method astask.setSucceded(missing ane) while its own JSDoc documented the correct spelling, so calling the documented name threw. Two callers already carried(sic)comments explaining the trap instead of fixing it. Renamed with all 17 call sites updated; no alias kept, since the extensions were audited and none use the task API.Testing
unit:MigrateAssist, 15 specs: sunset math, origin predicates, path-root validation, the security refusals above, and a byte-exact streaming round trip using a file containingNULand0xFF.Before this ships
LEGACY_ORIGINis stillhttps://staging.phcode.devfor validation. Flipping it tophcode.devis a one-line change inconstants.js.phcode.devmust allowweb.phcode.devto frame/migrateAssist.html. If the host blocks framing the handshake times out and the migration silently never runs.x.y.zversion, not the build number, so existing users only pick up a deploy when the version bumps.