Repository navigation
Let an app crate supply its update source through AppExtensions - #3410
Conversation
Pragmatic code review (round 1)Review Type: Initial Review Reviewed at Requirements Check:
Code Review Findings: What's good:
Recommendation: APPROVE No blockers or important issues. The design follows the existing extension-point pattern and keeps the extensions module free of edition reads. Both callers converge on one source-reading path, and every new test was mutation-checked. All seven findings are optional polish. |
mstomar125
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review: APPROVE (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
Address review (round 1)Review verdict: APPROVE, with 7 optional findings and no blockers. Addressed in ✅ Addressed (5)
⏭️ Skipped (2)
Checks after the fixes
Re-Review DecisionDecision: NO RE-REVIEW NEEDED Rationale: Every change is documentation, a test name, a test assertion message, or one |
#3320) The update check's version source and the banner's download URL were chosen inside check_for_update by branching on the build edition. They are now an UpdateSource held in managed state, so an app built on core decides both. - update_check.rs: UpdateSource / LatestVersionSource with UpdateSource::community(); builtin_update_source_for(is_pro) holds today's two sources in one place; UpdateSourceState, update_source_for_app and check_for_update_for_app read the managed source. The fetchers are generic (fetch_github_latest_release(repo), fetch_version_endpoint(url), version_from_endpoint_json). check_for_update, LATEST_RELEASE_URL and the edition-specific fetcher and parser are deleted; check_for_update_command delegates to check_for_update_for_app. - extensions: AppExtensions::update_source (last call wins); assemble manages the extension's source or the built-in one. UpdateSource and LatestVersionSource are re-exported from extensions and the crate root. - lib.rs: the startup update check calls check_for_update_for_app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ivate item Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ad URL scheme - Docs that named UpdateSource::community as the built-in source now say it is the built-in source in a default build. - UpdateSource::download_url and AppExtensions::update_source say the URL must be http or https, the only schemes the banner's opener can open. - check_for_update_for_app logs at debug level when a source yields no version, so a mistyped extension source can be diagnosed. - The localhost end-to-end test names the proxy variables when the local endpoint gives no answer. - builtin_source_is_community_outside_pro_builds is renamed builtin_source_without_the_edition_flag_is_community, matching its sibling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
30a34da to
2cafd48
Compare
Closes #3320
An app crate built on core can now decide where the update check looks and what the update banner's Download button opens, through
AppExtensions::update_source(UpdateSource)(ADR-082 (proposed) §3.6). WithAppExtensions::none()every build behaves exactly as before.What changed
app-lib/src/update_check.rsUpdateSource { latest: LatestVersionSource, download_url: Option<&'static str> }andLatestVersionSource::{GitHubLatestRelease { repo }, VersionEndpoint { url }}(Clone, Debug, PartialEq, Eq, with rustdoc), plusUpdateSource::community()(const fn).builtin_update_source_for(is_pro)holds today's behaviour in one place:falseiscommunity()(the core repo's GitHub latest release, downloaded from the releases page),trueis the in-core version endpoint with no download URL, verbatim from Carry the download URL in the update-check payload instead of a hardcoded releases page #3319.UpdateSourceState(managed state),update_source_for_app(app)(the managed source, or the built-in one when none is managed), andcheck_for_update_for_app(app), which reads the version fromapp.package_info(), fetches from the source, and callsstatus_from(current, latest, source.download_url).github_latest_release_url(repo),fetch_github_latest_release(repo),fetch_version_endpoint(url),version_from_endpoint_json(body)(renamed from the edition-specific parser) andfetch_latest_version(&LatestVersionSource).check_for_update,LATEST_RELEASE_URL,fetch_latest_tag,fetch_pro_latest_versionand the old parser.check_for_update_commanddelegates tocheck_for_update_for_app. The module doc names the update source generically.app-lib/src/extensions/mod.rsAppExtensions::update_source(source); the last call wins. Its rustdoc states the contract: the source is fixed when the app is built, the running version comes from the bundle config, aVersionEndpointmust answer{"version": "<semver or v-tag>"}, anddownload_url: Nonehides Download.assemblemanagesUpdateSourceState(extension's source, or builtin_update_source()), as its own statement before the existingExtensionHooksmanage, so the PRs that add more extension points after this one don't collide with it.UpdateSourceandLatestVersionSourceare re-exported fromextensionsand from the crate root.app-lib/src/lib.rs: the startup spawn callsupdate_check::check_for_update_for_app(&update_app_handle); thecurrent_versionlocal is gone.Deviations from the issue
RELEASES_PAGE_URL, notCOMMUNITY_RELEASES_PAGE_URL. That's the name Carry the download URL in the update-check payload instead of a hardcoded releases page #3319 actually merged with, socommunity()uses it. Its repo is the new constantCORE_REPO.assembledoesn't read the edition itself. The issue hasassemblecallbuiltin_update_source_for(is_pro_build()). Instead, a no-argumentbuiltin_update_source()inupdate_check.rsmakes that call, and bothassembleand the unmanaged fallback inupdate_source_for_appuse it. This keeps the extensions module free of edition reads, and leaves thebuiltin_update_source_forargument as the file's onlyis_pro_build()read, as the acceptance criterion asks.BASELINESare not lowered. The issue's step 5 says to lower them; the standing instruction for this drain is not to, because parallel PRs collide on those lines and Drive every Pro-boundary ratchet count to zero and make the check a hard ban #3371 tightens them all at once.builtin_source_with_the_edition_flag_is_the_in_core_endpoint_without_a_download(pins acceptance criterion 2; Delete the Pro arm of core's built-in update source and its worker parsing #3323 deletes it with thetruearm, and it won't compile once the argument goes),update_source_falls_back_to_the_builtin_without_extension_state, andan_extension_update_source_decides_the_version_and_the_download. The last one drivescheck_for_update_for_append to end against a one-shot HTTP server on127.0.0.1, so it never touches the internet.builtin_source_is_community_outside_pro_buildsis namedbuiltin_source_without_the_edition_flag_is_community, so new code carries no product name, as the issue's last acceptance criterion asks. The new name also mirrors its sibling test.lib.rs.both_update_checks_read_the_app_update_sourcepins the startup spawn inrun's source andcheck_for_update_command's body inupdate_check.rs. The command takes a concretetauri::AppHandle(Wry) and can't run on the mock runtime.Pro-boundary ratchet
bun run scripts/check-pro-boundary.tspasses.BASELINESare untouched.Every other marker is unchanged.
editionBranchingrises by 2 (96 → 98), class 3: a planned move of Pro code already in core that adds no behaviour. The in-core edition branch moves out ofcheck_for_update(one hit removed) intobuiltin_update_source_for, which the issue specifies with anis_pro: boolparameter. The added lines:#3323 deletes the
truearm and the parameter, which removes all three. The count stays below its baseline, so nothing is raised.Tests
Run locally:
cargo test --lib -p nodespace-app-lib -- update_check extensions:: run_wiring --test-threads=2: 50 passed.bun run test:changed(rust and scripts tiers): passed; the app-lib unit binary passed 290 of 290.cargo clippy -p nodespace-app-lib --all-targets -- -D warnings -D clippy::unused-async: clean.NODESPACE_PRO_SUPABASE_URL=https://example.invalid NODESPACE_PRO_ANON_KEY=dummy cargo check -p nodespace-app: passes.bun run scripts/check-pro-boundary.ts: passes (see above).New and renamed tests. Each was mutation-checked: I broke the code it covers, confirmed it fails, then restored.
update_check::community_source_targets_the_public_release_endpointgithub_latest_release_urldrops/latestupdate_check::community_source_downloads_from_the_releases_pagecommunity()hasdownload_url: Noneupdate_check::builtin_source_without_the_edition_flag_is_communityupdate_check::builtin_source_with_the_edition_flag_is_the_in_core_endpoint_without_a_downloadupdate_check::version_endpoint_body_parsed(renamed)tag_nameinstead ofversionupdate_check::version_endpoint_error_body_yields_none(renamed)versionextensions::fixture_tests::none_manages_the_builtin_update_sourceassembledoesn't manage the sourceextensions::fixture_tests::an_extension_update_source_replaces_the_builtinupdate_source()ignores its argumentextensions::fixture_tests::the_last_update_source_winsget_or_insert)extensions::fixture_tests::update_source_falls_back_to_the_builtin_without_extension_stateextensions::fixture_tests::an_extension_update_source_decides_the_version_and_the_downloadcheck_for_update_for_apppassesNoneas the download URLrun_wiring_tests::both_update_checks_read_the_app_update_sourcecheck_for_update_for_app(fails on the startup assertion); the command builds its status from the built-in source (fails on the command assertion)The existing update-check tests, including #3319's, pass unchanged.
Note for #3323
After this PR, #3323 deletes only
PRO_LATEST_URLand its doc, thetruearm andis_proparameter ofbuiltin_update_source_for, and the test pinning that arm. The version-endpoint parser and fetcher are now the genericVersionEndpointhelpers, and they stay.🤖 Generated with Claude Code