Skip to content

feat(career): first-launch milestone + download-size disclosure#1024

Closed
mhglover wants to merge 2 commits into
got-feedBack:mainfrom
mhglover:feat/career-prompts-achievements
Closed

feat(career): first-launch milestone + download-size disclosure#1024
mhglover wants to merge 2 commits into
got-feedBack:mainfrom
mhglover:feat/career-prompts-achievements

Conversation

@mhglover

@mhglover mhglover commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Draft — stacked on feedBack#1023 (venue packs). Review/merge that first; this branch includes its commits until then. Part of the nightly-slimming effort (feedBack-desktop#122).

Wires the opt-in content model into the career UI.

What's here

  • Download-size disclosure — the "Download venue pack" button now states the size, e.g. "Download venue pack (~335 MB)", read live from the manifest (pack.bytes, exposed as pack_bytes in /state) so the number can't drift from the real asset. Absent/zero → no suffix (e.g. club until its release is published).
  • Career achievements — career is the first source plugin to use the achievements cross-plugin API. Registers career_started + first_venue_gig and unlocks them on first visit / first logged gig (competency only, idempotent, documented pending-queue contract — no new event bus).
  • No progress loss on upgradeunlocked is computed from stars (meta.db), independent of pack presence, so a previously-unlocked venue whose media is no longer bundled shows unlocked + downloadable, never locked/broken.

Tests (all green)

  • Upgrade / no-progress-loss: arena unlocked with media unbundled → reports unlocked + downloadable → _download_pack restores it.
  • formatBytes MB/GB rounding + empty-when-unknown.
  • Achievement wiring: boot() drains the pending queue → registers both, unlocks career_started once (not re-fired when already welcomed).
  • Full suite: 2727 passed (2 unrelated pre-existing packaging failures).

Notes

  • The rig "simulated rig?" prompt + rig achievements moved to the rig VST PR (PR 3), so they ship with the endpoint they call — nothing half-connected here.
  • No modal/gate: bar is bundled so entering career starts no download; consent lives on the download buttons (with sizes). The first visit just fires the career_started milestone (the achievements plugin surfaces its own toast).

Design intent (achievements)

These achievements reward real musician learning, not game busywork. The opt-in content model exists so the player grows into the game, and the achievements mark milestones on that real-world path:

  • Starting a career — committing to the practice loop.
  • Learning simulated amps/effects (rig_builder, PR 3) — deciding to dial in a tone.
  • Running a real rig alongside the game (PR 3) — graduating to physical gear.

The aim: playing the game rewards you for learning the craft. Contributors adding achievements here or in sibling plugins should keep to that spirit — celebrate a skill or decision a working musician would recognize. (Same note is in the code above CAREER_ACHIEVEMENTS.)

Summary by CodeRabbit

  • New Features

    • Added career achievements for starting the career journey and completing the first venue gig.
    • Download buttons now display the published pack size when available.
    • Added clearer handling for venues without a published downloadable pack.
  • Bug Fixes

    • Unlocked venues remain available for download after bundled media is removed.
    • Pack downloads now correctly reject unpublished or placeholder packs.
  • Tests

    • Added coverage for achievement registration, one-time welcome rewards, pack-size formatting, and venue downloads.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f75b481a-1ba7-4268-8a69-f08f1f333756

📥 Commits

Reviewing files that changed from the base of the PR and between 6d30bef and cba2aad.

📒 Files selected for processing (4)
  • plugins/career/routes.py
  • plugins/career/screen.js
  • plugins/career/tests/passports.test.js
  • tests/plugins/career/test_routes.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • plugins/career/tests/passports.test.js
  • plugins/career/screen.js
  • plugins/career/routes.py

📝 Walkthrough

Walkthrough

The career plugin now distinguishes published venue packs by committed size, exposes nullable pack sizes, gates downloads accordingly, displays known sizes, and integrates two career achievements with boot and gig lifecycle events.

Changes

Career runtime and achievements

Layer / File(s) Summary
Published-pack runtime behavior
plugins/career/routes.py, tests/plugins/career/test_routes.py
Pack availability and downloads now require a non-zero committed bytes value; unbundled venue media remains downloadable and becomes installed after download.
Career achievement and size UI
plugins/career/screen.js, plugins/career/tests/passports.test.js
Registers career_started and first_venue_gig, unlocks them at boot and gig completion, queues unavailable achievement API calls, formats known pack sizes, and tests the lifecycle.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CareerScreen
  participant AchievementsQueue
  participant AchievementsAPI
  participant LocalStorage

  CareerScreen->>AchievementsQueue: Queue registerAll and career_started unlock
  AchievementsQueue->>AchievementsAPI: Drain queued achievement operations
  CareerScreen->>LocalStorage: Read feedBack-career-welcomed
  CareerScreen->>AchievementsAPI: Unlock career_started once
  CareerScreen->>AchievementsAPI: Unlock first_venue_gig after gig completion
Loading

Possibly related PRs

Suggested reviewers: byrongamatos

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the change and tests, but it omits the required feedpak surface and checklist sections from the template. Add the template sections: What, feedpak surface with the required checkbox, and Checklist items for changelog, tests, and DCO sign-off.
Docstring Coverage ⚠️ Warning Docstring coverage is 31.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: a career milestone plus pack size disclosure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mhglover

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
plugins/career/tests/passports.test.js (1)

269-273: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove duplicated code.

The pending2.forEach line is duplicated three times.

♻️ Proposed fix
     const pending2 = w2.__feedBackAchievementsPending || [];
     const unlocked2 = [];
     pending2.forEach((fn) => fn({ register() {}, registerAll() {}, unlock: (id) => unlocked2.push(id) }));
-    pending2.forEach((fn) => fn({ register() {}, registerAll() {}, unlock: (id) => unlocked2.push(id) }));
-    pending2.forEach((fn) => fn({ register() {}, registerAll() {}, unlock: (id) => unlocked2.push(id) }));
     assert.deepEqual(unlocked2, []);
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/career/tests/passports.test.js` around lines 269 - 273, Remove the
repeated pending-achievement execution setup in the test by extracting the
shared pending2 iteration and unlock collection into a reusable local helper,
then use that helper for each duplicated assertion while preserving the existing
expected unlocked IDs.
tools/content_packs.py (1)

128-138: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid re-hashing the pack a second time.

build_pack()'s return value ({"sha256", "bytes"}) is discarded, then manifest_entry() re-reads the whole zip (out_zip.read_bytes()) and recomputes its sha256 from scratch just to get the same numbers. For a ~300MB+ venue pack (e.g. arena at ~335MB per venues.json), that's a second full read+hash pass per pack, in every CI build/publish run.

♻️ Proposed fix
     for src in args.src:
         pid = _pack_id(src)
         zip_path = out_dir / pack_asset(pid, args.version)
-        build_pack(src, zip_path)
+        info = build_pack(src, zip_path)
         if args.publish:
             publish(pid, args.version, zip_path)
             url = pack_url(pid, args.version)
         else:
             url = (out_dir.resolve() / zip_path.name).as_uri()
-        manifest[pid] = manifest_entry(zip_path, url)
+        manifest[pid] = {"url": url, **info}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/content_packs.py` around lines 128 - 138, Reuse the metadata returned
by build_pack() when constructing each manifest entry instead of discarding it.
Update manifest_entry() and its call in the surrounding pack loop to accept the
build result’s sha256 and bytes, avoiding any second read or hash of the
generated zip while preserving the existing URL and manifest format.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/content-packs.yml:
- Around line 42-54: Update the “Build & publish packs” step to pass the
workflow inputs through the step’s env block rather than interpolating
github.event.inputs.venues or github.event.inputs.version directly into run.
Read those environment variables inside the shell script, preserving the
existing directory construction and content_packs.py invocation while preventing
input values from being parsed as shell syntax.
- Around line 70-81: Scope the create-pull-request step around “Open
manifest-bump PR” to commit only the manifest file, excluding the untracked
_packs/ zip artifacts produced by the publish step. Configure the action’s file
selection input while preserving the existing branch, commit message, title, and
body.

---

Nitpick comments:
In `@plugins/career/tests/passports.test.js`:
- Around line 269-273: Remove the repeated pending-achievement execution setup
in the test by extracting the shared pending2 iteration and unlock collection
into a reusable local helper, then use that helper for each duplicated assertion
while preserving the existing expected unlocked IDs.

In `@tools/content_packs.py`:
- Around line 128-138: Reuse the metadata returned by build_pack() when
constructing each manifest entry instead of discarding it. Update
manifest_entry() and its call in the surrounding pack loop to accept the build
result’s sha256 and bytes, avoiding any second read or hash of the generated zip
while preserving the existing URL and manifest format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a6cf1cae-5b04-4281-bd4f-870f576554f3

📥 Commits

Reviewing files that changed from the base of the PR and between a9be210 and 6d30bef.

📒 Files selected for processing (7)
  • .github/workflows/content-packs.yml
  • plugins/career/routes.py
  • plugins/career/screen.js
  • plugins/career/tests/passports.test.js
  • plugins/career/venues.json
  • tests/plugins/career/test_routes.py
  • tools/content_packs.py

Comment thread .github/workflows/content-packs.yml
Comment on lines +70 to +81
- name: Open manifest-bump PR
uses: peter-evans/create-pull-request@v6
with:
commit-message: "career: refresh venue pack manifest (v${{ github.event.inputs.version }})"
title: "career: refresh venue pack manifest (v${{ github.event.inputs.version }})"
body: |
Automated by the content-packs workflow after publishing
`${{ github.event.inputs.venues }}` v${{ github.event.inputs.version }}
to their `venue-<id>-v${{ github.event.inputs.version }}` releases.
Bumps `venues.json` pack url/sha256/bytes to match the uploaded zips.
branch: content-packs/manifest-bump
delete-branch: true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== workflow snippet ==\n'
sed -n '1,140p' .github/workflows/content-packs.yml

printf '\n== tool outline ==\n'
ast-grep outline tools/content_packs.py --view expanded || true

printf '\n== tool search for _packs and local/publish ==\n'
rg -n "_packs|--local|--publish|create-pull-request|add-paths|venues.json" tools/content_packs.py .github/workflows/content-packs.yml

Repository: got-feedBack/feedBack

Length of output: 5197


🌐 Web query:

peter-evans/create-pull-request v6 add-paths default commit all modified untracked files workspace

💡 Result:

In peter-evans/create-pull-request v6, the add-paths input determines which files are included in the pull request commit [1][2]. By default, if no paths are specified, the action automatically adds all new (untracked) and modified (tracked) files present in the repository workspace [1][3][4]. When using the add-paths input, you can provide a comma or newline-separated list of file paths using git pathspec syntax [1][2]. Importantly, if you specify paths, any file changes in the workspace that do not match the provided paths are automatically stashed and restored after the action completes, ensuring only the intended files are committed to the pull request [1][2].

Citations:


🌐 Web query:

peter-evans/create-pull-request add-paths default all modified untracked files documentation

💡 Result:

The peter-evans/create-pull-request GitHub Action automatically detects and commits all new (untracked) and modified (tracked) files by default [1][2][3]. Regarding the add-paths input: 1. Default Behavior: If the add-paths input is omitted (left blank), the action automatically includes all modified and untracked files [1][2]. 2. Using add-paths: When you specify paths using the add-paths input, only the files that match the provided pathspec are committed [1][2]. 3. Handling Excluded Files: If you use add-paths to select specific files, any other changes in your workspace (tracked or untracked) that do not match your specified paths will be stashed and restored after the action finishes, ensuring they are not included in the pull request [1][2][4]. In summary, you do not need to explicitly configure anything to include all modified and untracked files; simply omitting the add-paths parameter achieves this by default [1][2].

Citations:


Scope the manifest-bump PR to the manifest file. peter-evans/create-pull-request@v6 will otherwise include the untracked _packs/ zip artifacts from the publish step, since it commits all modified and new files by default.

Suggested change
      - name: Open manifest-bump PR
        uses: peter-evans/create-pull-request@v6
        with:
          commit-message: "career: refresh venue pack manifest (v${{ github.event.inputs.version }})"
          title: "career: refresh venue pack manifest (v${{ github.event.inputs.version }})"
          body: |
            Automated by the content-packs workflow after publishing
            `${{ github.event.inputs.venues }}` v${{ github.event.inputs.version }}
            to their `venue-<id>-v${{ github.event.inputs.version }}` releases.
            Bumps `venues.json` pack url/sha256/bytes to match the uploaded zips.
+         add-paths: plugins/career/venues.json
          branch: content-packs/manifest-bump
          delete-branch: true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Open manifest-bump PR
uses: peter-evans/create-pull-request@v6
with:
commit-message: "career: refresh venue pack manifest (v${{ github.event.inputs.version }})"
title: "career: refresh venue pack manifest (v${{ github.event.inputs.version }})"
body: |
Automated by the content-packs workflow after publishing
`${{ github.event.inputs.venues }}` v${{ github.event.inputs.version }}
to their `venue-<id>-v${{ github.event.inputs.version }}` releases.
Bumps `venues.json` pack url/sha256/bytes to match the uploaded zips.
branch: content-packs/manifest-bump
delete-branch: true
- name: Open manifest-bump PR
uses: peter-evans/create-pull-request@v6
with:
commit-message: "career: refresh venue pack manifest (v${{ github.event.inputs.version }})"
title: "career: refresh venue pack manifest (v${{ github.event.inputs.version }})"
body: |
Automated by the content-packs workflow after publishing
`${{ github.event.inputs.venues }}` v${{ github.event.inputs.version }}
to their `venue-<id>-v${{ github.event.inputs.version }}` releases.
Bumps `venues.json` pack url/sha256/bytes to match the uploaded zips.
add-paths: plugins/career/venues.json
branch: content-packs/manifest-bump
delete-branch: true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/content-packs.yml around lines 70 - 81, Scope the
create-pull-request step around “Open manifest-bump PR” to commit only the
manifest file, excluding the untracked _packs/ zip artifacts produced by the
publish step. Configure the action’s file selection input while preserving the
existing branch, commit message, title, and body.

mhglover and others added 2 commits July 22, 2026 18:27
Wire the opt-in content model into the career UI:

- Download-size disclosure: the "Download venue pack" button now states the
  pack size (e.g. "Download venue pack (~335 MB)"), read live from the
  manifest (`pack.bytes`, exposed as `pack_bytes` in /state) so the number
  can't drift from the real asset. Absent/zero size → no suffix.
- Career achievements: career is the first source plugin to use the
  achievements cross-plugin API. Registers career_started + first_venue_gig
  and unlocks them on first visit / first logged gig (competency only,
  idempotent). No new event bus; uses the documented pending-queue contract.
- No progress loss on upgrade: verified `unlocked` is computed from stars
  (meta.db), independent of pack presence — a previously-unlocked venue whose
  media is no longer bundled shows unlocked + downloadable, never locked.

Tests: upgrade/no-progress-loss (arena unlocked with media unbundled →
downloadable → restored); formatBytes MB/GB; achievement wiring drains the
pending queue and fires career_started once. All green.

Stacked on the venue-packs PR (feedBack#1023). Part of #122.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Matthew Harris Glover <matthew@harrisglover.com>
…ian learning

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Matthew Harris Glover <matthew@harrisglover.com>
@mhglover
mhglover force-pushed the feat/career-prompts-achievements branch from 6d30bef to cba2aad Compare July 22, 2026 22:29
@mhglover
mhglover marked this pull request as ready for review July 22, 2026 22:36
byrongamatos added a commit that referenced this pull request Jul 22, 2026
Rebased onto merged main (was stacked on #1023/#1024, whose venue work is
now in main) so it no longer carries a stale content_packs.py that would
revert 1023's build_pack fixes.

- build_vst_pack: slice a fat .vst3 tree to one platform (keep its binary
  dir + shared bundle files, drop the two foreign platform dirs and src/
  build trees). Pins create_system=3 like build_pack — without it the same
  tree hashes differently on a Windows runner (native .vst3 are built there),
  breaking the precomputable-hash guarantee exactly where it matters.
- Publish wiring: 'python tools/content_packs.py <vst-root> --vst --version N
  --publish' builds+uploads vst-<plat>-vN releases for mac/win/linux and emits
  a platform-keyed {url,sha256,bytes} manifest — the shape rig_builder's
  data/vst_packs.json consumes. publish() refactored onto a shared
  _publish_release helper (venue behaviour unchanged).
- Tests: slice keeps target+shared/drops foreign, per-platform binary,
  reproducibility, unknown-platform reject, and a simulated-win32 guard that
  fails if the create_system pin is dropped. selfcheck covers the VST path.

Original build_vst_pack by Matthew Harris Glover; reworked for the create_system
fix, publish wiring, and rebase.

Signed-off-by: byrongamatos <xasiklas@gmail.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mhglover mhglover closed this Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant