Skip to content

fix(submit): preserve multi-machine submissions without reshaping daily breakdown#389

Open
junhoyeo wants to merge 12 commits intomainfrom
fix/source-scoped-multi-machine-submissions
Open

fix(submit): preserve multi-machine submissions without reshaping daily breakdown#389
junhoyeo wants to merge 12 commits intomainfrom
fix/source-scoped-multi-machine-submissions

Conversation

@junhoyeo
Copy link
Copy Markdown
Owner

@junhoyeo junhoyeo commented Apr 1, 2026

Summary

  • preserve multi-machine submissions by scoping submissions rows with source_id / source_name instead of collapsing every account into a single row
  • upgrade a lone legacy unsourced row in place on the first source-aware submit to avoid duplicate totals during cutover
  • aggregate profile, embed, and leaderboard reads across all submission rows for the same user
  • keep daily_breakdown.source_breakdown flat and avoid bundling #329-style /api/me/stats / remote TUI sync scope

Why

main stored a single submissions row per user and /api/submit selected that row by userId only. That meant a later submit from another machine could overwrite overlapping flat per-client breakdowns for the same user/day instead of preserving both machines' contributions.

What changed

Database / schema

  • add submissions.source_id
  • add submissions.source_name
  • drop single-row-per-user uniqueness
  • add one unsourced-row-per-user partial unique index
  • add unique (user_id, source_id) for source-scoped rows
  • keep daily_breakdown.source_breakdown unchanged

Submit flow

  • accept optional meta.sourceId / meta.sourceName
  • normalize blank source metadata to undefined
  • resolve submit target rows by source scope
  • upgrade a lone legacy unsourced row in place on first source-aware submit
  • return 409 when a scoped account later submits without source identity
  • keep per-day merge behavior on the existing flat client breakdown shape

Read aggregation

  • aggregate profile totals across all submission rows
  • aggregate embed totals / submission count / rank across all submission rows
  • aggregate leaderboard submission counts consistently across all submission rows

CLI

  • persist a stable source ID locally
  • support TOKSCALE_SOURCE_ID
  • support TOKSCALE_SOURCE_NAME
  • include source metadata in submit payloads

Scope notes

  • intentionally does not bundle #329-style /api/me/stats
  • intentionally does not bundle remote TUI sync changes
  • intentionally preserves the current DB and daily_breakdown.source_breakdown shape

Verification

  • cargo fmt --all --check
  • cargo clippy -p tokscale-cli --all-features -- -D warnings
  • cargo test -p tokscale-cli
  • bunx vitest run packages/frontend/__tests__/api/submit.test.ts packages/frontend/__tests__/api/submitAuth.test.ts packages/frontend/__tests__/api/usersProfile.test.ts packages/frontend/__tests__/lib/dbHelpers.test.ts packages/frontend/__tests__/lib/getUserEmbedStats.test.ts
  • bunx vitest run packages/frontend/__tests__/lib/getLeaderboard.test.ts packages/frontend/__tests__/lib/getLeaderboardAllTime.test.ts
  • targeted frontend eslint on changed files

Known residual risks

  • older unsourced clients will receive 409 after an account enters source-scoped mode
  • a missed read path could still assume one submissions row per user
  • submission_hash uniqueness under source scoping may need a follow-up decision
  • no source-management UI exists beyond submitted sourceName

Rollout notes

  • deploy server + migration before relying on new CLI source metadata
  • verify first source-aware submit upgrades a lone legacy unsourced row in place
  • verify later unsourced submit returns the expected 409
  • verify second source-aware machine/source aggregates instead of overwriting

Open with Devin

Summary by cubic

Scopes submissions by stable source identity to preserve multi‑machine submits without changing the daily_breakdown shape. Adds device views and per‑source APIs (list, detail, lightweight summary), uses the summary endpoint for device previews, aggregates reads across rows, and namespaces route-facing source keys to avoid __legacy__ collisions.

  • New Features

    • Submission scoping by (user_id, source_id) with in‑place upgrade for a lone legacy unsourced row; returns 409 when a scoped account submits without source identity; per‑day merge unchanged.
    • Aggregated reads across all rows for profile, embed, and leaderboard, including summed submission counts.
    • APIs: /api/users/[username]/sources (summaries), /api/users/[username]/sources/[sourceId] (detail), and /api/users/[username]/sources/[sourceId]/summary (lightweight per‑source summary); keep daily_breakdown.source_breakdown flat.
    • Profile UI: new “Devices” tab with per‑source totals and recent activity; selected device preview now uses the /sources/[sourceId]/summary endpoint.
    • CLI (tokscale-cli): persist a stable source ID at ~/.config/tokscale/source-id with PID+age lock and stale timeout; include meta.sourceId/meta.sourceName in submit/export; support TOKSCALE_SOURCE_ID and TOKSCALE_SOURCE_NAME.
    • Source keys: namespace real source IDs in routes and decode/normalize in APIs to avoid collisions with the legacy unsourced sentinel.
  • Migration

    • Add submissions.source_id/source_name; drop single‑row‑per‑user uniqueness; add partial unique index for unsourced rows and unique (user_id, source_id) for scoped rows; no change to daily_breakdown.source_breakdown.

Written for commit 13d13e8. Summary will update on new commits.

junhoyeo added 2 commits April 2, 2026 07:35
…ly breakdown

Mainline stored a single submission row per user, so a later submit from another machine could overwrite flat per-client breakdowns for overlapping days. This scopes submissions by stable source identity, upgrades a lone legacy unsourced row in place on first source-aware submit, and aggregates profile/embed/leaderboard reads across rows while keeping the existing daily_breakdown source_breakdown shape flat.

Constraint: Must keep the current database and existing daily_breakdown.source_breakdown shape
Constraint: Must remain compatible with legacy unsourced rows during cutover
Rejected: Nested per-device JSON in daily_breakdown | broader read-path churn and token-identity pitfalls
Rejected: Bundle /api/me/stats and remote TUI sync | unrelated scope increase for the overwrite fix
Confidence: high
Scope-risk: moderate
Reversibility: messy
Directive: Keep embed/profile/leaderboard reads source-row aware; do not reintroduce single-row-per-user assumptions
Tested: cargo fmt --all --check; cargo clippy -p tokscale-cli --all-features -- -D warnings; cargo test -p tokscale-cli; bunx vitest run packages/frontend/__tests__/api/submit.test.ts packages/frontend/__tests__/api/submitAuth.test.ts packages/frontend/__tests__/api/usersProfile.test.ts packages/frontend/__tests__/lib/dbHelpers.test.ts packages/frontend/__tests__/lib/getUserEmbedStats.test.ts; bunx vitest run packages/frontend/__tests__/lib/getLeaderboard.test.ts packages/frontend/__tests__/lib/getLeaderboardAllTime.test.ts; targeted frontend eslint on changed files
Not-tested: Full frontend typecheck remains blocked by the pre-existing packages/frontend/src/components/BlackholeHero.tsx asset import typing error; live database migration rehearsal against production-like Postgres
…h PR review

This reverts commit 344c05d from main
so the multi-machine submission change can land through the normal
PR path instead of a direct push.

Constraint: User requested reverting the direct push and reopening the change as a PR
Constraint: Must restore main without losing the already-validated patch
Rejected: Force-reset main | destructive history rewrite on a published branch
Rejected: Leave change on main and open a follow-up PR | does not satisfy the requested rollback
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the replacement PR branch aligned with commit 344c05d content; do not sneak in extra scope during re-land
Tested: git revert --no-commit 344c05d; git status review
Not-tested: Re-running the full verification matrix after reverting main (revert only removes the already-tested patch)
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tokscale Ready Ready Preview, Comment Apr 2, 2026 2:09am

Request Review

Copy link
Copy Markdown
Owner Author

junhoyeo commented Apr 1, 2026

Rollout checklist for this PR:

  • deploy server + DB migration before relying on new CLI source metadata
  • verify first source-aware submit upgrades a lone legacy unsourced row in place
  • verify later unsourced submit returns the expected 409
  • verify second source-aware machine/source aggregates instead of overwriting
  • watch for any read path still assuming one submissions row per user
  • decide whether submission_hash uniqueness needs a follow-up rule under source scoping

Verification completed before opening the PR:

  • cargo fmt --all --check
  • cargo clippy -p tokscale-cli --all-features -- -D warnings
  • cargo test -p tokscale-cli
  • focused frontend Vitest suites for submit/profile/embed/leaderboard/db helpers
  • targeted frontend ESLint on changed files

Known unrelated pre-existing issue:

  • full frontend typecheck still fails on packages/frontend/src/components/BlackholeHero.tsx asset-import typing

Copy link
Copy Markdown
Owner Author

junhoyeo commented Apr 1, 2026

Release-note friendly summary:

  • Preserve multi-machine submissions with additive source-scoped submission rows
  • Keep daily_breakdown.source_breakdown flat; avoid nested device JSON churn
  • Upgrade a lone legacy unsourced row in place on first source-aware submit
  • Aggregate profile, embed, and leaderboard reads across all submission rows
  • Add stable CLI source identity support via sourceId / sourceName
  • Return 409 for ambiguous unsourced submits after scoped mode begins

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 18 files

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a50538060a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Owner Author

junhoyeo commented Apr 1, 2026

Suggested short merge description:

Preserve multi-machine submissions by adding additive source-scoped submission rows, upgrading a lone legacy unsourced row in place on first source-aware submit, and aggregating profile/embed/leaderboard reads across rows while keeping daily_breakdown.source_breakdown flat.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 7 additional findings.

Open in Devin Review

IvGolovach and others added 4 commits April 2, 2026 07:57
…ests

Constraint: Keep the cherry-picked PR #388 snapshot lint-clean under this repo's frontend ESLint rules
Rejected: Leave the original variable name | fails @next/next/no-assign-module-variable in local lint
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this as a tiny follow-up on top of the original authored commits; do not fold broader changes into the credit-preserving rewrite
Tested: packages/frontend/node_modules/.bin/eslint --config packages/frontend/eslint.config.mjs packages/frontend/__tests__/lib/getUserEmbedStats.test.ts
Not-tested: Full frontend verification matrix (history rewrite only; behavior unchanged from prior verified branch)
@junhoyeo junhoyeo force-pushed the fix/source-scoped-multi-machine-submissions branch from a505380 to a339495 Compare April 1, 2026 22:57
Copy link
Copy Markdown
Owner Author

junhoyeo commented Apr 1, 2026

Credit note: this PR branch now preserves the original authored work from #388 by @IvGolovach via cherry-pick history, instead of a single squashed commit.

Preserved authored commits on this branch:

  • e4d7999fix(submit): preserve source-scoped multi-machine submissions
  • 79f7d99fix(submit): harden source-scoped submission flow
  • ea8520ffix(submit): use portable source-scoped submission constraints

Small follow-up by me on top:

  • a339495test(embed): avoid reserved module variable in source-scoped submit tests

That means commit-level authorship is now properly attributed to the original author, and the branch keeps a small separate follow-up for the local lint-only rename.

Copy link
Copy Markdown
Owner Author

junhoyeo commented Apr 1, 2026

Attribution note:

This PR is based on and now preserves the original authored work from #388 by @IvGolovach. The branch history was rewritten so the original source-scoped submission commits are carried forward directly, with one small follow-up commit by me for local lint compatibility in a test file.

Original authored work preserved here:

  • e4d7999 — preserve source-scoped multi-machine submissions
  • 79f7d99 — harden source-scoped submission flow
  • ea8520f — use portable source-scoped submission constraints

Small follow-up on top:

  • a339495 — rename a reserved module test variable to satisfy the local Next/ESLint rule

So the implementation credit should primarily go to @IvGolovach / #388, with my contribution limited to the branch re-land + the tiny lint-only follow-up.

junhoyeo added 2 commits April 2, 2026 08:01
The source-id lock previously trusted a matching live PID indefinitely. If the PID had been recycled by an unrelated process, an old lock file could block first-time source ID generation until submit fell back to unsourced payloads.

Constraint: Source-id initialization should stay resilient without introducing a broader lock format migration
Rejected: Trust PID liveness forever | stale lock can survive PID reuse and block initialization
Rejected: Remove any lock older than the short stale threshold | risks breaking a legitimately active locker on a slow filesystem
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep a hard age-based escape hatch even when PID probes report alive; PID alone is not a stable lock owner identity
Tested: cargo fmt --all --check; cargo clippy -p tokscale-cli --all-features -- -D warnings; cargo test -p tokscale-cli test_should_remove_stale_source_id_lock -- --nocapture
Not-tested: Full tokscale-cli test suite rerun after this narrow auth-lock change
Source-scoped submissions make it possible to inspect usage by machine, so
this adds a dedicated sources/devices view on profile pages and a matching
API that aggregates per-source totals and recent contribution history.

Constraint: Must build on the source-scoped submission model without reshaping daily_breakdown.source_breakdown
Constraint: Must fit the existing profile page flow with minimal extra round-trips
Rejected: Force all source detail into the existing /api/users/[username] payload | keeps the core profile response leaner and separates concerns
Rejected: Wait for a separate per-source detail API before shipping UI | unnecessary delay for a useful first device view
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep device/source viewing aligned with source_id as the stable identity and source_name as display-only metadata
Tested: bunx vitest run packages/frontend/__tests__/api/userSources.test.ts packages/frontend/__tests__/api/usersProfile.test.ts; packages/frontend/node_modules/.bin/eslint --config packages/frontend/eslint.config.mjs src/app/u/[username]/page.tsx src/app/u/[username]/ProfilePageClient.tsx src/app/api/users/[username]/sources/route.ts src/components/profile/index.tsx __tests__/api/userSources.test.ts
Not-tested: Full frontend typecheck still blocked by the pre-existing packages/frontend/src/components/BlackholeHero.tsx asset import typing error
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/frontend/src/app/u/[username]/page.tsx">

<violation number="1" location="packages/frontend/src/app/u/[username]/page.tsx:71">
P2: A thrown error from the new `getSourceData` call will reject `Promise.all` and fail the whole profile page render instead of falling back to an empty sources list.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +71 to +74
const [data, sourceData] = await Promise.all([
getProfileData(username),
getSourceData(username),
]);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 2, 2026

Choose a reason for hiding this comment

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

P2: A thrown error from the new getSourceData call will reject Promise.all and fail the whole profile page render instead of falling back to an empty sources list.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/src/app/u/[username]/page.tsx, line 71:

<comment>A thrown error from the new `getSourceData` call will reject `Promise.all` and fail the whole profile page render instead of falling back to an empty sources list.</comment>

<file context>
@@ -52,11 +68,19 @@ export async function generateMetadata({ params }: { params: Promise<{ username:
 export default async function ProfilePage({ params }: { params: Promise<{ username: string }> }) {
   const { username } = await params;
-  const data = await getProfileData(username);
+  const [data, sourceData] = await Promise.all([
+    getProfileData(username),
+    getSourceData(username),
</file context>
Suggested change
const [data, sourceData] = await Promise.all([
getProfileData(username),
getSourceData(username),
]);
const [data, sourceData] = await Promise.all([
getProfileData(username),
getSourceData(username).catch(() => ({ sources: [] })),
]);
Fix with Cubic

The first device-view pass bundled summary and detail payloads into one
sources endpoint. This separates the lightweight source list from the
heavier per-source detail response so profile pages can show device cards
without shipping full contribution histories for every machine up front.

Constraint: Must keep source/device views aligned with the source-scoped submission model already on this branch
Constraint: Must avoid bloating the profile page payload with every source's full contribution history
Rejected: Keep a single /sources endpoint with embedded detail for all sources | unnecessary payload growth and tighter coupling between card list and detail graph
Rejected: Drop server-side initial source detail entirely | worse first-load UX for the default selected source
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep /sources for summaries and /sources/[sourceId] for detailed histories; if more device UI is added, build on this separation rather than rejoining the payloads
Tested: bunx vitest run packages/frontend/__tests__/api/userSources.test.ts packages/frontend/__tests__/api/userSourceDetail.test.ts packages/frontend/__tests__/api/usersProfile.test.ts; packages/frontend/node_modules/.bin/eslint --config packages/frontend/eslint.config.mjs src/app/u/[username]/page.tsx src/app/u/[username]/ProfilePageClient.tsx src/app/api/users/[username]/sources/route.ts src/app/api/users/[username]/sources/[sourceId]/route.ts src/app/api/users/[username]/sources/shared.ts src/components/profile/index.tsx __tests__/api/userSources.test.ts __tests__/api/userSourceDetail.test.ts
Not-tested: Full frontend typecheck remains blocked by the pre-existing packages/frontend/src/components/BlackholeHero.tsx asset import typing error
Copy link
Copy Markdown
Owner Author

junhoyeo commented Apr 2, 2026

Update: the source/device profile work on this branch has been refined.

What changed on top of the previous device-view pass:

  • split the original /api/users/[username]/sources payload into:
    • GET /api/users/[username]/sources for lightweight source summaries/cards
    • GET /api/users/[username]/sources/[sourceId] for per-source detail (history, models, breakdown)
  • keep the profile page default UX by server-fetching the initially selected source detail
  • client now switches device cards against the detail endpoint instead of carrying full histories for every source in the summary payload

New commit:

  • 3032e38feat(profile): split source summaries from source detail views

Verification for this follow-up:

  • bunx vitest run packages/frontend/__tests__/api/userSources.test.ts packages/frontend/__tests__/api/userSourceDetail.test.ts packages/frontend/__tests__/api/usersProfile.test.ts
  • targeted frontend eslint on the new routes / profile files

Known unchanged pre-existing issue:

  • full frontend typecheck still fails on packages/frontend/src/components/BlackholeHero.tsx asset-import typing

The device/source view now has lightweight summary and detail endpoints,
but external consumers still need a compact per-source payload for cards,
embeds, badges, or quick previews. This adds a dedicated summary route so
clients can fetch one source's headline metrics without pulling the full
contribution history.

Constraint: Must build on the split source summary/detail API shape already on this branch
Constraint: Must stay lightweight and avoid returning the full per-day history payload
Rejected: Reuse the full source detail endpoint for summary consumers | unnecessary payload size for badge/embed/preview use cases
Rejected: Add source summary fields only to the top-level user profile API | couples a focused source capability back into a broader profile response
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep source summary routes compact; if more source-level consumers appear, expand this route before bloating the detail response
Tested: bunx vitest run packages/frontend/__tests__/api/userSources.test.ts packages/frontend/__tests__/api/userSourceDetail.test.ts packages/frontend/__tests__/api/userSourceSummary.test.ts packages/frontend/__tests__/api/usersProfile.test.ts; packages/frontend/node_modules/.bin/eslint --config packages/frontend/eslint.config.mjs src/app/api/users/[username]/sources/route.ts src/app/api/users/[username]/sources/[sourceId]/route.ts src/app/api/users/[username]/sources/[sourceId]/summary/route.ts src/app/api/users/[username]/sources/shared.ts __tests__/api/userSources.test.ts __tests__/api/userSourceDetail.test.ts __tests__/api/userSourceSummary.test.ts
Not-tested: Full frontend typecheck remains blocked by the pre-existing packages/frontend/src/components/BlackholeHero.tsx asset import typing error
Copy link
Copy Markdown
Owner Author

junhoyeo commented Apr 2, 2026

Follow-up update on the same branch:

  • added GET /api/users/[username]/sources/[sourceId]/summary
  • purpose: a compact per-source payload for cards / previews / potential badge/embed use cases without returning full contribution history
  • this now gives the branch a three-level shape:
    • /api/users/[username]/sources → source summaries list
    • /api/users/[username]/sources/[sourceId] → full detail/history
    • /api/users/[username]/sources/[sourceId]/summary → lightweight source headline metrics

New commit:

  • 74cf2b7feat(profile): add lightweight source summary endpoint

Verification for this follow-up:

  • bunx vitest run packages/frontend/__tests__/api/userSources.test.ts packages/frontend/__tests__/api/userSourceDetail.test.ts packages/frontend/__tests__/api/userSourceSummary.test.ts packages/frontend/__tests__/api/usersProfile.test.ts
  • targeted frontend eslint on the source routes/tests

Known unchanged pre-existing issue:

  • full frontend typecheck still fails on packages/frontend/src/components/BlackholeHero.tsx asset-import typing

The branch already exposed a lightweight source summary route, but the
profile UI still consumed only the summary list and full detail payloads.
This wires the selected device panel to fetch and display a compact
preview from `/sources/[sourceId]/summary`, so the new endpoint is used
for actual UI affordances rather than existing only for future consumers.

Constraint: Must reuse the lightweight source summary endpoint instead of duplicating top-client/top-model derivation in the client
Constraint: Must preserve the existing default selected-device UX
Rejected: Continue showing only the full detail panel | leaves the new summary endpoint unused by the profile UI
Rejected: Move summary-only fields back into the source list payload | defeats the endpoint separation introduced earlier
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep quick-preview metadata sourced from the summary endpoint so summary/detail responsibilities stay distinct
Tested: bunx vitest run packages/frontend/__tests__/api/userSourceSummary.test.ts packages/frontend/__tests__/api/userSources.test.ts packages/frontend/__tests__/api/userSourceDetail.test.ts packages/frontend/__tests__/api/usersProfile.test.ts; packages/frontend/node_modules/.bin/eslint --config packages/frontend/eslint.config.mjs src/app/u/[username]/page.tsx src/app/u/[username]/ProfilePageClient.tsx src/app/api/users/[username]/sources/[sourceId]/summary/route.ts __tests__/api/userSourceSummary.test.ts
Not-tested: Full frontend typecheck remains blocked by the pre-existing packages/frontend/src/components/BlackholeHero.tsx asset import typing error
Copy link
Copy Markdown
Owner Author

junhoyeo commented Apr 2, 2026

Another small follow-up landed on the same branch:

  • the profile Devices tab now uses GET /api/users/[username]/sources/[sourceId]/summary to render a compact quick-preview card for the currently selected source/device
  • that means the new summary endpoint is now exercised by real UI, not just exposed for future consumers

New commit:

  • 56e060ffeat(profile): show selected device preview from the summary endpoint

Quick-preview fields now shown from the summary route:

  • top client
  • top model
  • submission count
  • active days
  • updated date

Verification for this follow-up:

  • bunx vitest run packages/frontend/__tests__/api/userSourceSummary.test.ts packages/frontend/__tests__/api/userSources.test.ts packages/frontend/__tests__/api/userSourceDetail.test.ts packages/frontend/__tests__/api/usersProfile.test.ts
  • targeted frontend eslint on the summary route + profile page/client files

Known unchanged pre-existing issue:

  • full frontend typecheck still fails on packages/frontend/src/components/BlackholeHero.tsx asset-import typing

Copy link
Copy Markdown
Owner Author

junhoyeo commented Apr 2, 2026

Merge-ready summary:

This PR now covers three coherent layers of the same feature set:

  1. source-scoped multi-machine submission storage

  2. CLI/source identity hardening

    • stable sourceId / sourceName
    • stale source-id.lock now has a hard age-based cleanup escape hatch to avoid indefinite blockage from PID reuse
  3. profile device/source views

    • profile Devices tab
    • GET /api/users/[username]/sources for lightweight source summaries
    • GET /api/users/[username]/sources/[sourceId] for full source detail/history
    • GET /api/users/[username]/sources/[sourceId]/summary for compact source headline metrics
    • selected-device quick preview now consumes the summary endpoint in the real UI

Verification completed on this branch:

  • cargo fmt --all --check
  • cargo clippy -p tokscale-cli --all-features -- -D warnings
  • cargo test -p tokscale-cli
  • focused frontend Vitest suites for submit/profile/source summary/detail flows
  • targeted frontend eslint on changed files/routes

Known unchanged pre-existing issue:

  • full frontend typecheck is still blocked by packages/frontend/src/components/BlackholeHero.tsx asset-import typing

From a scope perspective, this is a good stopping point: the source-scoped submission change, its follow-up hardening, and the profile source/device UX now form a coherent, reviewable unit without drifting further into unrelated polish.

cubic-dev-ai[bot]

This comment was marked as resolved.

The source routes used `"__legacy__"` as both the unsourced sentinel and a
possible user-provided `sourceId`, which made a real `sourceId="__legacy__"`
collide with legacy rows. This namespaces real source keys with a prefix and
routes the detail API through the shared client alias normalizer.

Constraint: Must preserve support for legacy unsourced rows while keeping source URLs stable enough for the new profile UI
Rejected: Keep `__legacy__` as a raw dual-purpose key | real source rows can collide with the legacy sentinel and become ambiguous
Rejected: Introduce a random/non-deterministic surrogate per source key | makes routing and hydration harder for the profile UI
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Treat route-facing source keys as encoded transport values, not raw source IDs; all real IDs should remain namespaced away from the legacy sentinel
Tested: bunx vitest run packages/frontend/__tests__/api/userSources.test.ts packages/frontend/__tests__/api/userSourceDetail.test.ts packages/frontend/__tests__/api/userSourceSummary.test.ts packages/frontend/__tests__/api/usersProfile.test.ts; packages/frontend/node_modules/.bin/eslint --config packages/frontend/eslint.config.mjs src/app/api/users/[username]/sources/shared.ts src/app/api/users/[username]/sources/[sourceId]/route.ts __tests__/api/userSources.test.ts __tests__/api/userSourceDetail.test.ts __tests__/api/userSourceSummary.test.ts
Not-tested: Full frontend typecheck remains blocked by the pre-existing packages/frontend/src/components/BlackholeHero.tsx asset import typing error
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.

2 participants