feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3) - #1660

Merged
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation
Apr 24, 2026
Merged

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3)#1660
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Part 2 of 3 — salvaging codex/amicus-auth-access-hardening

Foundation layer for the Amicus v2 work split out of codex/amicus-auth-access-hardening. No UI changes — every touched component is either a DB schema migration, a pure-function service, a type definition, or a test.

Stacks with:

Why this is its own PR

The codex branch shipped these foundation pieces intermingled with 10 UI surfaces in one 58-file blob. Splitting them out gives us:

  • A reviewable DB change surface (2 migrations + a PRAGMA change) evaluated on its own merits
  • The ability to roll back UI wiring without losing the data model
  • Clean test signal: foundation-layer tests aren't entangled with screen render tests

What's in

DB

  • Migration 22 — amicus_thread_summaries — one row per thread, stores summary_text, last_user_intent, updated_at. Feeds the thread list so rows show meaningful labels without re-parsing every message.
  • Migration 23 — amicus_thread_context — ties threads to guided-study sessions, specific steps, open questions, takeaways, key connections, and entry points. CHECK constraints on entry_point (fab|peek|thread|home_card|guided_study|my_study) and guided_step (scene|observe|explore|synthesize|review). FK references to amicus_threads (ON DELETE CASCADE) and guided_study_sessions / guided_study_questions (ON DELETE SET NULL). Two indexes tuned for recent-first lookups by (session, step) and by updated timestamp.
  • PRAGMA foreign_keys = ON on every user-db connection open. Tier 2 fix from the senior review. SQLite leaves FK checks off by default and the setting is per-connection. Without it, every REFERENCES ... ON DELETE ... in our schema is cosmetic. This makes them real runtime constraints. Runs outside any transaction, before migrations, so migration DDL is unaffected — only DML that violates a declared FK gets rejected, which is what we want.

Services (all new, all pure functions)

FileWhat it does
services/amicus/context.tsbuildAmicusContextEnvelope, chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef, formatAmicusContextLabel, prettyBookId. Shared envelope the Worker proxy receives + client-side formatting.
services/amicus/trust.tssummarizeAmicusTrust reduces citations into source labels and stance; formatTrustStanceLabel.
services/amicus/threadIntelligence.tsderiveThreadIntelligence derives title/summary/intent from first user query + guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
services/amicus/studyActions.tsbuildStudyActionSeeds enumerates one-tap actions for chapter-aware Amicus surfaces (explain_context, investigate_question, interpretive_tensions, refine_takeaway, trace_connections).
services/amicus/studyLaunch.tslaunchAmicusStudyThread resolves the right thread to open (dedup by question > session/step > chapter+entrypoint) and navigates. promotePeekToAmicusThread hydrates a peek session into a full thread.
services/amicus/deepLink.tsformatChapterRef helper extended for the new envelope shape.
services/amicus/dailyPrompt.tsMinor signature update to accept the enriched context.
services/amicus/index.tsBarrel exports all of the above.

Types

  • types/user.tsAmicusThread extended with summary_text, last_user_intent, guided_step, open_question_id, guided_question_status, takeaway, key_connection (all hydrated by a single LEFT JOIN'd query). New AmicusThreadContextRecord and AmicusDraftMessage types.

Queries + mutations

  • db/userQueries.tslistAmicusThreads / getAmicusThread now LEFT JOIN amicus_thread_summaries, amicus_thread_context, and guided_study_questions so the UI can render enriched rows without N+1. New getters: getAmicusThreadContext, getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session}, getLatestAmicusThreadIdForChapterContext, getLatestStudyAmicusThreadIdForChapter, getGuidedStudyQuestionForSession.
  • db/userMutations.tsupsertAmicusThreadContext and upsertAmicusThreadSummary (proper ON CONFLICT DO UPDATE SET … = excluded.*). reopenGuidedStudyQuestion. deleteAmicusThread wrapped in a transaction with explicit amicus_thread_context delete (redundant with FK cascade now ON, but defensive against DBs that predated this PR's pragma). resetToNewUser extended. createAmicusThread uses serializeAmicusChapterRef.
  • Not in this PR: the emitAmicusUsageChanged calls in incrementAmicusUsage / clearAllAmicusData — those belong with the subscription in PR feat(amicus): harden auth access with RC init wiring (1/3) #1658 and are stripped here to keep the PRs independent.

Tests

  • New: services/amicus/__tests__/ for context, trust, threadIntelligence, studyActions, studyLaunch
  • Extended: deepLink, db/amicus/mutations + queries, utils/routeContext, db/userDatabaseV3 (migrations 22/23)

Tier 2 fix included

PRAGMA foreign_keys = ON — turns the FK references in migrations 22/23 (and all prior migrations that have declared FKs) into actual runtime constraints. Flagged during the senior review as a pre-existing latent issue this branch was amplifying; fixing it here makes the new schema honest.

Risks

FK enforcement on existing DBs. If earlier bugs wrote orphan rows that violate a declared FK, those rows stay (SQLite doesn't retroactively scan) — but any new operation that tries to reference a missing parent will start failing. Low risk in practice: existing migrations declare few FKs and those reference well-populated tables. Worth a simulator smoke pass on a user.db that has real-world usage before cutting a release.

Out of scope (moves to PR 3)

Test plan

  • tsc --noEmit + jest via CI
  • Manual smoke (rentamac iOS simulator):
    1. First launch — new user-db gets migrations 22/23 applied; sqlite_master shows both tables + both indexes
    2. PRAGMA foreign_keys returns 1 on a fresh connection
    3. Manually insert a bad FK (e.g. INSERT INTO amicus_thread_context(thread_id, entry_point) VALUES ('nope', 'fab')) — expect SQLITE_CONSTRAINT
    4. Delete an amicus_threads row that has a matching amicus_thread_context — verify CASCADE fires (context row gone)
    5. Existing app surfaces (notes, bookmarks, reading progress) continue to work — smoke each of the main screens

Rollback

Single-commit revert. Migrations 22 and 23 stay in sqlite_master (SQLite doesn't run migrations in reverse), but the code that reads/writes them is gone so the tables become inert. New thread-context/summary rows stop being written; existing ones orphan harmlessly. FK pragma goes back to off on the next release. No user data loss.

…ment
Part 2 of splitting codex/amicus-auth-access-hardening. This PR lands
the non-UI foundation for the Amicus v2 work: DB schema, type
definitions, new pure-function services, and the accompanying tests.
## What this does
### DB layer
- **Migration 22** — amicus_thread_summaries. Stores derived title/
summary and last-user-intent per thread so the thread list can show
meaningful context without re-reading every message.
- **Migration 23** — amicus_thread_context. Ties threads to guided-
study sessions, specific steps, open questions, takeaways, and
entry-point (FAB / peek / thread / home_card / guided_study /
my_study). CHECK constraints on enum fields, FK references to
amicus_threads (CASCADE) and guided_study_sessions / questions
(SET NULL). Two indexes: (session_id, step, updated_at DESC) and
(updated_at DESC) for recent-first lookups.
- **PRAGMA foreign_keys = ON** on every user-db connection open
(Tier 2 fix). SQLite leaves FK checks off by default and the
setting is per-connection — without this, every REFERENCES clause
in our schema is cosmetic. This turns them into real runtime
constraints. DDL is unaffected; only DML that would violate a
declared FK gets rejected.
### Services (pure functions, no UI)
- services/amicus/context.ts — buildAmicusContextEnvelope,
chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef,
formatAmicusContextLabel, prettyBookId. Shared envelope the proxy
receives plus client-side formatting.
- services/amicus/trust.ts — summarizeAmicusTrust reduces citations
into a trust summary the TrustFooter will render.
- services/amicus/threadIntelligence.ts — deriveThreadIntelligence
builds title/summary/lastUserIntent from the first user query +
guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
- services/amicus/studyActions.ts — buildStudyActionSeeds enumerates
one-tap actions shown in Amicus surfaces when a chapter is in view
(explain_context, investigate_question, interpretive_tensions,
refine_takeaway, trace_connections).
- services/amicus/studyLaunch.ts — launchAmicusStudyThread resolves
the right thread to open (dedup by question > session/step >
chapter+entrypoint) and navigates. promotePeekToAmicusThread
hydrates a peek session into a full thread with messages replayed.
- services/amicus/deepLink.ts — formatChapterRef helper extended.
- services/amicus/dailyPrompt.ts — minor signature update.
- services/amicus/index.ts — barrel exports all of the above.
### Types + queries + mutations
- types/user.ts — AmicusThread now carries summary/intent/guided-step
fields; new AmicusThreadContextRecord and AmicusDraftMessage types.
- db/userQueries.ts — list/get thread queries LEFT JOIN
amicus_thread_summaries, amicus_thread_context, and
guided_study_questions so the UI surfaces can render enriched rows
without N+1 queries. New getters: getAmicusThreadContext,
getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session},
getLatestAmicusThreadIdForChapterContext,
getLatestStudyAmicusThreadIdForChapter,
getGuidedStudyQuestionForSession.
- db/userMutations.ts — upsertAmicusThreadContext,
upsertAmicusThreadSummary, reopenGuidedStudyQuestion.
deleteAmicusThread wrapped in a transaction and explicit
amicus_thread_context delete (belt-and-suspenders: with foreign_keys
now ON this is redundant but doesn't hurt; without it on past
runs, it's the only path that cleans up). resetToNewUser extended
to include amicus_thread_context.
- Existing createAmicusThread uses serializeAmicusChapterRef for
consistent storage format.
### Tests
- services/amicus/__tests__/context, trust, threadIntelligence,
studyActions, studyLaunch — all new. deepLink test extended.
- db/amicus/mutations + queries tests extended to cover the new
thread-context surface.
- utils/routeContext.test extended for the new helper.
- db/userDatabaseV3 test extended for migrations 22/23.
## Tier 2 fix included
PRAGMA foreign_keys = ON on every user-db open. Turns the FK
references in migrations 22/23 (and every prior migration that has
declared FKs) into actual runtime constraints. Flagged during the
senior review as a pre-existing latent issue this branch was
amplifying; fixing it alongside makes the new schema honest.
## Out of scope (moves to PR 3)
- All UI components, hooks, screens, and navigation types
- StudySessionScreen changes (preserves #1654 bounce-back fix)
- MyStudyScreen changes (deferred per #1652 re-plan)
## Risks
- **FK enforcement on existing DBs.** If earlier bugs wrote orphan
rows that violate a declared FK, those rows stay (SQLite doesn't
retroactively scan), but any NEW operation that tries to reference
the missing parent will start failing. Low risk in practice — our
existing migrations declare few FKs and those reference well-
populated tables (user_id, thread_id, session_id). Worth a
simulator smoke pass on a user.db that has real-world usage.
## Rollback
Revert the single merge. Migrations 22 and 23 stay in the sqlite_master
table — SQLite doesn't run migrations in reverse — but the code
that reads/writes those tables is gone, so the tables become inert.
New thread-context/summary rows stop being written; existing ones
orphan harmlessly. The FK pragma goes back to off. No user data loss.
Stacks with PR 1 (independent, can merge in either order) and PR 3
(depends on this).
@github-actions

Copy link
Copy Markdown

⚠️Tests: Could not parse results

…to master
Two CI failures on PR 2:
1. deletePreference regression — the codex branch removed this function
from userMutations.ts but left the caller in settingsStore.ts intact.
When I pulled userMutations wholesale into PR 2, the removal came
along, breaking tsc in settingsStore at line 16 (missing import) and
cascading to line 179 (implicit-any on the now-typeless .catch(err)).
Restored from master.
2. dailyPrompt.ts imports getAmicusAuthToken from authToken.ts, which
lives in PR 1. PR 2 was built to stand alone, so the cross-PR import
can't resolve. Reverted dailyPrompt.ts to master's version. The auth
integration for dailyPrompt can land with PR 3 or as a follow-up
once both PR 1 and PR 2 merge.
…dation
# Conflicts:
#	app/src/db/userMutations.ts
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3670❌ 03670
Suites✅ 498❌ 0498

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 91.3s

@CraigBuckmaster
CraigBuckmaster merged commit 5159ae0 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the feat/amicus-v2-foundation branch April 24, 2026 21:04
Sign up for freeto 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

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3) - #1660

Merged
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation
Apr 24, 2026
Merged

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3)#1660
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Part 2 of 3 — salvaging codex/amicus-auth-access-hardening

Foundation layer for the Amicus v2 work split out of codex/amicus-auth-access-hardening. No UI changes — every touched component is either a DB schema migration, a pure-function service, a type definition, or a test.

Stacks with:

Why this is its own PR

The codex branch shipped these foundation pieces intermingled with 10 UI surfaces in one 58-file blob. Splitting them out gives us:

  • A reviewable DB change surface (2 migrations + a PRAGMA change) evaluated on its own merits
  • The ability to roll back UI wiring without losing the data model
  • Clean test signal: foundation-layer tests aren't entangled with screen render tests

What's in

DB

  • Migration 22 — amicus_thread_summaries — one row per thread, stores summary_text, last_user_intent, updated_at. Feeds the thread list so rows show meaningful labels without re-parsing every message.
  • Migration 23 — amicus_thread_context — ties threads to guided-study sessions, specific steps, open questions, takeaways, key connections, and entry points. CHECK constraints on entry_point (fab|peek|thread|home_card|guided_study|my_study) and guided_step (scene|observe|explore|synthesize|review). FK references to amicus_threads (ON DELETE CASCADE) and guided_study_sessions / guided_study_questions (ON DELETE SET NULL). Two indexes tuned for recent-first lookups by (session, step) and by updated timestamp.
  • PRAGMA foreign_keys = ON on every user-db connection open. Tier 2 fix from the senior review. SQLite leaves FK checks off by default and the setting is per-connection. Without it, every REFERENCES ... ON DELETE ... in our schema is cosmetic. This makes them real runtime constraints. Runs outside any transaction, before migrations, so migration DDL is unaffected — only DML that violates a declared FK gets rejected, which is what we want.

Services (all new, all pure functions)

FileWhat it does
services/amicus/context.tsbuildAmicusContextEnvelope, chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef, formatAmicusContextLabel, prettyBookId. Shared envelope the Worker proxy receives + client-side formatting.
services/amicus/trust.tssummarizeAmicusTrust reduces citations into source labels and stance; formatTrustStanceLabel.
services/amicus/threadIntelligence.tsderiveThreadIntelligence derives title/summary/intent from first user query + guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
services/amicus/studyActions.tsbuildStudyActionSeeds enumerates one-tap actions for chapter-aware Amicus surfaces (explain_context, investigate_question, interpretive_tensions, refine_takeaway, trace_connections).
services/amicus/studyLaunch.tslaunchAmicusStudyThread resolves the right thread to open (dedup by question > session/step > chapter+entrypoint) and navigates. promotePeekToAmicusThread hydrates a peek session into a full thread.
services/amicus/deepLink.tsformatChapterRef helper extended for the new envelope shape.
services/amicus/dailyPrompt.tsMinor signature update to accept the enriched context.
services/amicus/index.tsBarrel exports all of the above.

Types

  • types/user.tsAmicusThread extended with summary_text, last_user_intent, guided_step, open_question_id, guided_question_status, takeaway, key_connection (all hydrated by a single LEFT JOIN'd query). New AmicusThreadContextRecord and AmicusDraftMessage types.

Queries + mutations

  • db/userQueries.tslistAmicusThreads / getAmicusThread now LEFT JOIN amicus_thread_summaries, amicus_thread_context, and guided_study_questions so the UI can render enriched rows without N+1. New getters: getAmicusThreadContext, getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session}, getLatestAmicusThreadIdForChapterContext, getLatestStudyAmicusThreadIdForChapter, getGuidedStudyQuestionForSession.
  • db/userMutations.tsupsertAmicusThreadContext and upsertAmicusThreadSummary (proper ON CONFLICT DO UPDATE SET … = excluded.*). reopenGuidedStudyQuestion. deleteAmicusThread wrapped in a transaction with explicit amicus_thread_context delete (redundant with FK cascade now ON, but defensive against DBs that predated this PR's pragma). resetToNewUser extended. createAmicusThread uses serializeAmicusChapterRef.
  • Not in this PR: the emitAmicusUsageChanged calls in incrementAmicusUsage / clearAllAmicusData — those belong with the subscription in PR feat(amicus): harden auth access with RC init wiring (1/3) #1658 and are stripped here to keep the PRs independent.

Tests

  • New: services/amicus/__tests__/ for context, trust, threadIntelligence, studyActions, studyLaunch
  • Extended: deepLink, db/amicus/mutations + queries, utils/routeContext, db/userDatabaseV3 (migrations 22/23)

Tier 2 fix included

PRAGMA foreign_keys = ON — turns the FK references in migrations 22/23 (and all prior migrations that have declared FKs) into actual runtime constraints. Flagged during the senior review as a pre-existing latent issue this branch was amplifying; fixing it here makes the new schema honest.

Risks

FK enforcement on existing DBs. If earlier bugs wrote orphan rows that violate a declared FK, those rows stay (SQLite doesn't retroactively scan) — but any new operation that tries to reference a missing parent will start failing. Low risk in practice: existing migrations declare few FKs and those reference well-populated tables. Worth a simulator smoke pass on a user.db that has real-world usage before cutting a release.

Out of scope (moves to PR 3)

Test plan

  • tsc --noEmit + jest via CI
  • Manual smoke (rentamac iOS simulator):
    1. First launch — new user-db gets migrations 22/23 applied; sqlite_master shows both tables + both indexes
    2. PRAGMA foreign_keys returns 1 on a fresh connection
    3. Manually insert a bad FK (e.g. INSERT INTO amicus_thread_context(thread_id, entry_point) VALUES ('nope', 'fab')) — expect SQLITE_CONSTRAINT
    4. Delete an amicus_threads row that has a matching amicus_thread_context — verify CASCADE fires (context row gone)
    5. Existing app surfaces (notes, bookmarks, reading progress) continue to work — smoke each of the main screens

Rollback

Single-commit revert. Migrations 22 and 23 stay in sqlite_master (SQLite doesn't run migrations in reverse), but the code that reads/writes them is gone so the tables become inert. New thread-context/summary rows stop being written; existing ones orphan harmlessly. FK pragma goes back to off on the next release. No user data loss.

…ment
Part 2 of splitting codex/amicus-auth-access-hardening. This PR lands
the non-UI foundation for the Amicus v2 work: DB schema, type
definitions, new pure-function services, and the accompanying tests.
## What this does
### DB layer
- **Migration 22** — amicus_thread_summaries. Stores derived title/
summary and last-user-intent per thread so the thread list can show
meaningful context without re-reading every message.
- **Migration 23** — amicus_thread_context. Ties threads to guided-
study sessions, specific steps, open questions, takeaways, and
entry-point (FAB / peek / thread / home_card / guided_study /
my_study). CHECK constraints on enum fields, FK references to
amicus_threads (CASCADE) and guided_study_sessions / questions
(SET NULL). Two indexes: (session_id, step, updated_at DESC) and
(updated_at DESC) for recent-first lookups.
- **PRAGMA foreign_keys = ON** on every user-db connection open
(Tier 2 fix). SQLite leaves FK checks off by default and the
setting is per-connection — without this, every REFERENCES clause
in our schema is cosmetic. This turns them into real runtime
constraints. DDL is unaffected; only DML that would violate a
declared FK gets rejected.
### Services (pure functions, no UI)
- services/amicus/context.ts — buildAmicusContextEnvelope,
chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef,
formatAmicusContextLabel, prettyBookId. Shared envelope the proxy
receives plus client-side formatting.
- services/amicus/trust.ts — summarizeAmicusTrust reduces citations
into a trust summary the TrustFooter will render.
- services/amicus/threadIntelligence.ts — deriveThreadIntelligence
builds title/summary/lastUserIntent from the first user query +
guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
- services/amicus/studyActions.ts — buildStudyActionSeeds enumerates
one-tap actions shown in Amicus surfaces when a chapter is in view
(explain_context, investigate_question, interpretive_tensions,
refine_takeaway, trace_connections).
- services/amicus/studyLaunch.ts — launchAmicusStudyThread resolves
the right thread to open (dedup by question > session/step >
chapter+entrypoint) and navigates. promotePeekToAmicusThread
hydrates a peek session into a full thread with messages replayed.
- services/amicus/deepLink.ts — formatChapterRef helper extended.
- services/amicus/dailyPrompt.ts — minor signature update.
- services/amicus/index.ts — barrel exports all of the above.
### Types + queries + mutations
- types/user.ts — AmicusThread now carries summary/intent/guided-step
fields; new AmicusThreadContextRecord and AmicusDraftMessage types.
- db/userQueries.ts — list/get thread queries LEFT JOIN
amicus_thread_summaries, amicus_thread_context, and
guided_study_questions so the UI surfaces can render enriched rows
without N+1 queries. New getters: getAmicusThreadContext,
getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session},
getLatestAmicusThreadIdForChapterContext,
getLatestStudyAmicusThreadIdForChapter,
getGuidedStudyQuestionForSession.
- db/userMutations.ts — upsertAmicusThreadContext,
upsertAmicusThreadSummary, reopenGuidedStudyQuestion.
deleteAmicusThread wrapped in a transaction and explicit
amicus_thread_context delete (belt-and-suspenders: with foreign_keys
now ON this is redundant but doesn't hurt; without it on past
runs, it's the only path that cleans up). resetToNewUser extended
to include amicus_thread_context.
- Existing createAmicusThread uses serializeAmicusChapterRef for
consistent storage format.
### Tests
- services/amicus/__tests__/context, trust, threadIntelligence,
studyActions, studyLaunch — all new. deepLink test extended.
- db/amicus/mutations + queries tests extended to cover the new
thread-context surface.
- utils/routeContext.test extended for the new helper.
- db/userDatabaseV3 test extended for migrations 22/23.
## Tier 2 fix included
PRAGMA foreign_keys = ON on every user-db open. Turns the FK
references in migrations 22/23 (and every prior migration that has
declared FKs) into actual runtime constraints. Flagged during the
senior review as a pre-existing latent issue this branch was
amplifying; fixing it alongside makes the new schema honest.
## Out of scope (moves to PR 3)
- All UI components, hooks, screens, and navigation types
- StudySessionScreen changes (preserves #1654 bounce-back fix)
- MyStudyScreen changes (deferred per #1652 re-plan)
## Risks
- **FK enforcement on existing DBs.** If earlier bugs wrote orphan
rows that violate a declared FK, those rows stay (SQLite doesn't
retroactively scan), but any NEW operation that tries to reference
the missing parent will start failing. Low risk in practice — our
existing migrations declare few FKs and those reference well-
populated tables (user_id, thread_id, session_id). Worth a
simulator smoke pass on a user.db that has real-world usage.
## Rollback
Revert the single merge. Migrations 22 and 23 stay in the sqlite_master
table — SQLite doesn't run migrations in reverse — but the code
that reads/writes those tables is gone, so the tables become inert.
New thread-context/summary rows stop being written; existing ones
orphan harmlessly. The FK pragma goes back to off. No user data loss.
Stacks with PR 1 (independent, can merge in either order) and PR 3
(depends on this).
@github-actions

Copy link
Copy Markdown

⚠️Tests: Could not parse results

…to master
Two CI failures on PR 2:
1. deletePreference regression — the codex branch removed this function
from userMutations.ts but left the caller in settingsStore.ts intact.
When I pulled userMutations wholesale into PR 2, the removal came
along, breaking tsc in settingsStore at line 16 (missing import) and
cascading to line 179 (implicit-any on the now-typeless .catch(err)).
Restored from master.
2. dailyPrompt.ts imports getAmicusAuthToken from authToken.ts, which
lives in PR 1. PR 2 was built to stand alone, so the cross-PR import
can't resolve. Reverted dailyPrompt.ts to master's version. The auth
integration for dailyPrompt can land with PR 3 or as a follow-up
once both PR 1 and PR 2 merge.
…dation
# Conflicts:
#	app/src/db/userMutations.ts
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3670❌ 03670
Suites✅ 498❌ 0498

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 91.3s

@CraigBuckmaster
CraigBuckmaster merged commit 5159ae0 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the feat/amicus-v2-foundation branch April 24, 2026 21:04
Sign up for freeto 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

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3) - #1660

Merged
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation
Apr 24, 2026
Merged

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3)#1660
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Part 2 of 3 — salvaging codex/amicus-auth-access-hardening

Foundation layer for the Amicus v2 work split out of codex/amicus-auth-access-hardening. No UI changes — every touched component is either a DB schema migration, a pure-function service, a type definition, or a test.

Stacks with:

Why this is its own PR

The codex branch shipped these foundation pieces intermingled with 10 UI surfaces in one 58-file blob. Splitting them out gives us:

  • A reviewable DB change surface (2 migrations + a PRAGMA change) evaluated on its own merits
  • The ability to roll back UI wiring without losing the data model
  • Clean test signal: foundation-layer tests aren't entangled with screen render tests

What's in

DB

  • Migration 22 — amicus_thread_summaries — one row per thread, stores summary_text, last_user_intent, updated_at. Feeds the thread list so rows show meaningful labels without re-parsing every message.
  • Migration 23 — amicus_thread_context — ties threads to guided-study sessions, specific steps, open questions, takeaways, key connections, and entry points. CHECK constraints on entry_point (fab|peek|thread|home_card|guided_study|my_study) and guided_step (scene|observe|explore|synthesize|review). FK references to amicus_threads (ON DELETE CASCADE) and guided_study_sessions / guided_study_questions (ON DELETE SET NULL). Two indexes tuned for recent-first lookups by (session, step) and by updated timestamp.
  • PRAGMA foreign_keys = ON on every user-db connection open. Tier 2 fix from the senior review. SQLite leaves FK checks off by default and the setting is per-connection. Without it, every REFERENCES ... ON DELETE ... in our schema is cosmetic. This makes them real runtime constraints. Runs outside any transaction, before migrations, so migration DDL is unaffected — only DML that violates a declared FK gets rejected, which is what we want.

Services (all new, all pure functions)

FileWhat it does
services/amicus/context.tsbuildAmicusContextEnvelope, chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef, formatAmicusContextLabel, prettyBookId. Shared envelope the Worker proxy receives + client-side formatting.
services/amicus/trust.tssummarizeAmicusTrust reduces citations into source labels and stance; formatTrustStanceLabel.
services/amicus/threadIntelligence.tsderiveThreadIntelligence derives title/summary/intent from first user query + guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
services/amicus/studyActions.tsbuildStudyActionSeeds enumerates one-tap actions for chapter-aware Amicus surfaces (explain_context, investigate_question, interpretive_tensions, refine_takeaway, trace_connections).
services/amicus/studyLaunch.tslaunchAmicusStudyThread resolves the right thread to open (dedup by question > session/step > chapter+entrypoint) and navigates. promotePeekToAmicusThread hydrates a peek session into a full thread.
services/amicus/deepLink.tsformatChapterRef helper extended for the new envelope shape.
services/amicus/dailyPrompt.tsMinor signature update to accept the enriched context.
services/amicus/index.tsBarrel exports all of the above.

Types

  • types/user.tsAmicusThread extended with summary_text, last_user_intent, guided_step, open_question_id, guided_question_status, takeaway, key_connection (all hydrated by a single LEFT JOIN'd query). New AmicusThreadContextRecord and AmicusDraftMessage types.

Queries + mutations

  • db/userQueries.tslistAmicusThreads / getAmicusThread now LEFT JOIN amicus_thread_summaries, amicus_thread_context, and guided_study_questions so the UI can render enriched rows without N+1. New getters: getAmicusThreadContext, getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session}, getLatestAmicusThreadIdForChapterContext, getLatestStudyAmicusThreadIdForChapter, getGuidedStudyQuestionForSession.
  • db/userMutations.tsupsertAmicusThreadContext and upsertAmicusThreadSummary (proper ON CONFLICT DO UPDATE SET … = excluded.*). reopenGuidedStudyQuestion. deleteAmicusThread wrapped in a transaction with explicit amicus_thread_context delete (redundant with FK cascade now ON, but defensive against DBs that predated this PR's pragma). resetToNewUser extended. createAmicusThread uses serializeAmicusChapterRef.
  • Not in this PR: the emitAmicusUsageChanged calls in incrementAmicusUsage / clearAllAmicusData — those belong with the subscription in PR feat(amicus): harden auth access with RC init wiring (1/3) #1658 and are stripped here to keep the PRs independent.

Tests

  • New: services/amicus/__tests__/ for context, trust, threadIntelligence, studyActions, studyLaunch
  • Extended: deepLink, db/amicus/mutations + queries, utils/routeContext, db/userDatabaseV3 (migrations 22/23)

Tier 2 fix included

PRAGMA foreign_keys = ON — turns the FK references in migrations 22/23 (and all prior migrations that have declared FKs) into actual runtime constraints. Flagged during the senior review as a pre-existing latent issue this branch was amplifying; fixing it here makes the new schema honest.

Risks

FK enforcement on existing DBs. If earlier bugs wrote orphan rows that violate a declared FK, those rows stay (SQLite doesn't retroactively scan) — but any new operation that tries to reference a missing parent will start failing. Low risk in practice: existing migrations declare few FKs and those reference well-populated tables. Worth a simulator smoke pass on a user.db that has real-world usage before cutting a release.

Out of scope (moves to PR 3)

Test plan

  • tsc --noEmit + jest via CI
  • Manual smoke (rentamac iOS simulator):
    1. First launch — new user-db gets migrations 22/23 applied; sqlite_master shows both tables + both indexes
    2. PRAGMA foreign_keys returns 1 on a fresh connection
    3. Manually insert a bad FK (e.g. INSERT INTO amicus_thread_context(thread_id, entry_point) VALUES ('nope', 'fab')) — expect SQLITE_CONSTRAINT
    4. Delete an amicus_threads row that has a matching amicus_thread_context — verify CASCADE fires (context row gone)
    5. Existing app surfaces (notes, bookmarks, reading progress) continue to work — smoke each of the main screens

Rollback

Single-commit revert. Migrations 22 and 23 stay in sqlite_master (SQLite doesn't run migrations in reverse), but the code that reads/writes them is gone so the tables become inert. New thread-context/summary rows stop being written; existing ones orphan harmlessly. FK pragma goes back to off on the next release. No user data loss.

…ment
Part 2 of splitting codex/amicus-auth-access-hardening. This PR lands
the non-UI foundation for the Amicus v2 work: DB schema, type
definitions, new pure-function services, and the accompanying tests.
## What this does
### DB layer
- **Migration 22** — amicus_thread_summaries. Stores derived title/
summary and last-user-intent per thread so the thread list can show
meaningful context without re-reading every message.
- **Migration 23** — amicus_thread_context. Ties threads to guided-
study sessions, specific steps, open questions, takeaways, and
entry-point (FAB / peek / thread / home_card / guided_study /
my_study). CHECK constraints on enum fields, FK references to
amicus_threads (CASCADE) and guided_study_sessions / questions
(SET NULL). Two indexes: (session_id, step, updated_at DESC) and
(updated_at DESC) for recent-first lookups.
- **PRAGMA foreign_keys = ON** on every user-db connection open
(Tier 2 fix). SQLite leaves FK checks off by default and the
setting is per-connection — without this, every REFERENCES clause
in our schema is cosmetic. This turns them into real runtime
constraints. DDL is unaffected; only DML that would violate a
declared FK gets rejected.
### Services (pure functions, no UI)
- services/amicus/context.ts — buildAmicusContextEnvelope,
chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef,
formatAmicusContextLabel, prettyBookId. Shared envelope the proxy
receives plus client-side formatting.
- services/amicus/trust.ts — summarizeAmicusTrust reduces citations
into a trust summary the TrustFooter will render.
- services/amicus/threadIntelligence.ts — deriveThreadIntelligence
builds title/summary/lastUserIntent from the first user query +
guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
- services/amicus/studyActions.ts — buildStudyActionSeeds enumerates
one-tap actions shown in Amicus surfaces when a chapter is in view
(explain_context, investigate_question, interpretive_tensions,
refine_takeaway, trace_connections).
- services/amicus/studyLaunch.ts — launchAmicusStudyThread resolves
the right thread to open (dedup by question > session/step >
chapter+entrypoint) and navigates. promotePeekToAmicusThread
hydrates a peek session into a full thread with messages replayed.
- services/amicus/deepLink.ts — formatChapterRef helper extended.
- services/amicus/dailyPrompt.ts — minor signature update.
- services/amicus/index.ts — barrel exports all of the above.
### Types + queries + mutations
- types/user.ts — AmicusThread now carries summary/intent/guided-step
fields; new AmicusThreadContextRecord and AmicusDraftMessage types.
- db/userQueries.ts — list/get thread queries LEFT JOIN
amicus_thread_summaries, amicus_thread_context, and
guided_study_questions so the UI surfaces can render enriched rows
without N+1 queries. New getters: getAmicusThreadContext,
getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session},
getLatestAmicusThreadIdForChapterContext,
getLatestStudyAmicusThreadIdForChapter,
getGuidedStudyQuestionForSession.
- db/userMutations.ts — upsertAmicusThreadContext,
upsertAmicusThreadSummary, reopenGuidedStudyQuestion.
deleteAmicusThread wrapped in a transaction and explicit
amicus_thread_context delete (belt-and-suspenders: with foreign_keys
now ON this is redundant but doesn't hurt; without it on past
runs, it's the only path that cleans up). resetToNewUser extended
to include amicus_thread_context.
- Existing createAmicusThread uses serializeAmicusChapterRef for
consistent storage format.
### Tests
- services/amicus/__tests__/context, trust, threadIntelligence,
studyActions, studyLaunch — all new. deepLink test extended.
- db/amicus/mutations + queries tests extended to cover the new
thread-context surface.
- utils/routeContext.test extended for the new helper.
- db/userDatabaseV3 test extended for migrations 22/23.
## Tier 2 fix included
PRAGMA foreign_keys = ON on every user-db open. Turns the FK
references in migrations 22/23 (and every prior migration that has
declared FKs) into actual runtime constraints. Flagged during the
senior review as a pre-existing latent issue this branch was
amplifying; fixing it alongside makes the new schema honest.
## Out of scope (moves to PR 3)
- All UI components, hooks, screens, and navigation types
- StudySessionScreen changes (preserves #1654 bounce-back fix)
- MyStudyScreen changes (deferred per #1652 re-plan)
## Risks
- **FK enforcement on existing DBs.** If earlier bugs wrote orphan
rows that violate a declared FK, those rows stay (SQLite doesn't
retroactively scan), but any NEW operation that tries to reference
the missing parent will start failing. Low risk in practice — our
existing migrations declare few FKs and those reference well-
populated tables (user_id, thread_id, session_id). Worth a
simulator smoke pass on a user.db that has real-world usage.
## Rollback
Revert the single merge. Migrations 22 and 23 stay in the sqlite_master
table — SQLite doesn't run migrations in reverse — but the code
that reads/writes those tables is gone, so the tables become inert.
New thread-context/summary rows stop being written; existing ones
orphan harmlessly. The FK pragma goes back to off. No user data loss.
Stacks with PR 1 (independent, can merge in either order) and PR 3
(depends on this).
@github-actions

Copy link
Copy Markdown

⚠️Tests: Could not parse results

…to master
Two CI failures on PR 2:
1. deletePreference regression — the codex branch removed this function
from userMutations.ts but left the caller in settingsStore.ts intact.
When I pulled userMutations wholesale into PR 2, the removal came
along, breaking tsc in settingsStore at line 16 (missing import) and
cascading to line 179 (implicit-any on the now-typeless .catch(err)).
Restored from master.
2. dailyPrompt.ts imports getAmicusAuthToken from authToken.ts, which
lives in PR 1. PR 2 was built to stand alone, so the cross-PR import
can't resolve. Reverted dailyPrompt.ts to master's version. The auth
integration for dailyPrompt can land with PR 3 or as a follow-up
once both PR 1 and PR 2 merge.
…dation
# Conflicts:
#	app/src/db/userMutations.ts
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3670❌ 03670
Suites✅ 498❌ 0498

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 91.3s

@CraigBuckmaster
CraigBuckmaster merged commit 5159ae0 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the feat/amicus-v2-foundation branch April 24, 2026 21:04
Sign up for freeto 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

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3) - #1660

Merged
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation
Apr 24, 2026
Merged

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3)#1660
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Part 2 of 3 — salvaging codex/amicus-auth-access-hardening

Foundation layer for the Amicus v2 work split out of codex/amicus-auth-access-hardening. No UI changes — every touched component is either a DB schema migration, a pure-function service, a type definition, or a test.

Stacks with:

Why this is its own PR

The codex branch shipped these foundation pieces intermingled with 10 UI surfaces in one 58-file blob. Splitting them out gives us:

  • A reviewable DB change surface (2 migrations + a PRAGMA change) evaluated on its own merits
  • The ability to roll back UI wiring without losing the data model
  • Clean test signal: foundation-layer tests aren't entangled with screen render tests

What's in

DB

  • Migration 22 — amicus_thread_summaries — one row per thread, stores summary_text, last_user_intent, updated_at. Feeds the thread list so rows show meaningful labels without re-parsing every message.
  • Migration 23 — amicus_thread_context — ties threads to guided-study sessions, specific steps, open questions, takeaways, key connections, and entry points. CHECK constraints on entry_point (fab|peek|thread|home_card|guided_study|my_study) and guided_step (scene|observe|explore|synthesize|review). FK references to amicus_threads (ON DELETE CASCADE) and guided_study_sessions / guided_study_questions (ON DELETE SET NULL). Two indexes tuned for recent-first lookups by (session, step) and by updated timestamp.
  • PRAGMA foreign_keys = ON on every user-db connection open. Tier 2 fix from the senior review. SQLite leaves FK checks off by default and the setting is per-connection. Without it, every REFERENCES ... ON DELETE ... in our schema is cosmetic. This makes them real runtime constraints. Runs outside any transaction, before migrations, so migration DDL is unaffected — only DML that violates a declared FK gets rejected, which is what we want.

Services (all new, all pure functions)

FileWhat it does
services/amicus/context.tsbuildAmicusContextEnvelope, chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef, formatAmicusContextLabel, prettyBookId. Shared envelope the Worker proxy receives + client-side formatting.
services/amicus/trust.tssummarizeAmicusTrust reduces citations into source labels and stance; formatTrustStanceLabel.
services/amicus/threadIntelligence.tsderiveThreadIntelligence derives title/summary/intent from first user query + guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
services/amicus/studyActions.tsbuildStudyActionSeeds enumerates one-tap actions for chapter-aware Amicus surfaces (explain_context, investigate_question, interpretive_tensions, refine_takeaway, trace_connections).
services/amicus/studyLaunch.tslaunchAmicusStudyThread resolves the right thread to open (dedup by question > session/step > chapter+entrypoint) and navigates. promotePeekToAmicusThread hydrates a peek session into a full thread.
services/amicus/deepLink.tsformatChapterRef helper extended for the new envelope shape.
services/amicus/dailyPrompt.tsMinor signature update to accept the enriched context.
services/amicus/index.tsBarrel exports all of the above.

Types

  • types/user.tsAmicusThread extended with summary_text, last_user_intent, guided_step, open_question_id, guided_question_status, takeaway, key_connection (all hydrated by a single LEFT JOIN'd query). New AmicusThreadContextRecord and AmicusDraftMessage types.

Queries + mutations

  • db/userQueries.tslistAmicusThreads / getAmicusThread now LEFT JOIN amicus_thread_summaries, amicus_thread_context, and guided_study_questions so the UI can render enriched rows without N+1. New getters: getAmicusThreadContext, getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session}, getLatestAmicusThreadIdForChapterContext, getLatestStudyAmicusThreadIdForChapter, getGuidedStudyQuestionForSession.
  • db/userMutations.tsupsertAmicusThreadContext and upsertAmicusThreadSummary (proper ON CONFLICT DO UPDATE SET … = excluded.*). reopenGuidedStudyQuestion. deleteAmicusThread wrapped in a transaction with explicit amicus_thread_context delete (redundant with FK cascade now ON, but defensive against DBs that predated this PR's pragma). resetToNewUser extended. createAmicusThread uses serializeAmicusChapterRef.
  • Not in this PR: the emitAmicusUsageChanged calls in incrementAmicusUsage / clearAllAmicusData — those belong with the subscription in PR feat(amicus): harden auth access with RC init wiring (1/3) #1658 and are stripped here to keep the PRs independent.

Tests

  • New: services/amicus/__tests__/ for context, trust, threadIntelligence, studyActions, studyLaunch
  • Extended: deepLink, db/amicus/mutations + queries, utils/routeContext, db/userDatabaseV3 (migrations 22/23)

Tier 2 fix included

PRAGMA foreign_keys = ON — turns the FK references in migrations 22/23 (and all prior migrations that have declared FKs) into actual runtime constraints. Flagged during the senior review as a pre-existing latent issue this branch was amplifying; fixing it here makes the new schema honest.

Risks

FK enforcement on existing DBs. If earlier bugs wrote orphan rows that violate a declared FK, those rows stay (SQLite doesn't retroactively scan) — but any new operation that tries to reference a missing parent will start failing. Low risk in practice: existing migrations declare few FKs and those reference well-populated tables. Worth a simulator smoke pass on a user.db that has real-world usage before cutting a release.

Out of scope (moves to PR 3)

Test plan

  • tsc --noEmit + jest via CI
  • Manual smoke (rentamac iOS simulator):
    1. First launch — new user-db gets migrations 22/23 applied; sqlite_master shows both tables + both indexes
    2. PRAGMA foreign_keys returns 1 on a fresh connection
    3. Manually insert a bad FK (e.g. INSERT INTO amicus_thread_context(thread_id, entry_point) VALUES ('nope', 'fab')) — expect SQLITE_CONSTRAINT
    4. Delete an amicus_threads row that has a matching amicus_thread_context — verify CASCADE fires (context row gone)
    5. Existing app surfaces (notes, bookmarks, reading progress) continue to work — smoke each of the main screens

Rollback

Single-commit revert. Migrations 22 and 23 stay in sqlite_master (SQLite doesn't run migrations in reverse), but the code that reads/writes them is gone so the tables become inert. New thread-context/summary rows stop being written; existing ones orphan harmlessly. FK pragma goes back to off on the next release. No user data loss.

…ment
Part 2 of splitting codex/amicus-auth-access-hardening. This PR lands
the non-UI foundation for the Amicus v2 work: DB schema, type
definitions, new pure-function services, and the accompanying tests.
## What this does
### DB layer
- **Migration 22** — amicus_thread_summaries. Stores derived title/
summary and last-user-intent per thread so the thread list can show
meaningful context without re-reading every message.
- **Migration 23** — amicus_thread_context. Ties threads to guided-
study sessions, specific steps, open questions, takeaways, and
entry-point (FAB / peek / thread / home_card / guided_study /
my_study). CHECK constraints on enum fields, FK references to
amicus_threads (CASCADE) and guided_study_sessions / questions
(SET NULL). Two indexes: (session_id, step, updated_at DESC) and
(updated_at DESC) for recent-first lookups.
- **PRAGMA foreign_keys = ON** on every user-db connection open
(Tier 2 fix). SQLite leaves FK checks off by default and the
setting is per-connection — without this, every REFERENCES clause
in our schema is cosmetic. This turns them into real runtime
constraints. DDL is unaffected; only DML that would violate a
declared FK gets rejected.
### Services (pure functions, no UI)
- services/amicus/context.ts — buildAmicusContextEnvelope,
chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef,
formatAmicusContextLabel, prettyBookId. Shared envelope the proxy
receives plus client-side formatting.
- services/amicus/trust.ts — summarizeAmicusTrust reduces citations
into a trust summary the TrustFooter will render.
- services/amicus/threadIntelligence.ts — deriveThreadIntelligence
builds title/summary/lastUserIntent from the first user query +
guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
- services/amicus/studyActions.ts — buildStudyActionSeeds enumerates
one-tap actions shown in Amicus surfaces when a chapter is in view
(explain_context, investigate_question, interpretive_tensions,
refine_takeaway, trace_connections).
- services/amicus/studyLaunch.ts — launchAmicusStudyThread resolves
the right thread to open (dedup by question > session/step >
chapter+entrypoint) and navigates. promotePeekToAmicusThread
hydrates a peek session into a full thread with messages replayed.
- services/amicus/deepLink.ts — formatChapterRef helper extended.
- services/amicus/dailyPrompt.ts — minor signature update.
- services/amicus/index.ts — barrel exports all of the above.
### Types + queries + mutations
- types/user.ts — AmicusThread now carries summary/intent/guided-step
fields; new AmicusThreadContextRecord and AmicusDraftMessage types.
- db/userQueries.ts — list/get thread queries LEFT JOIN
amicus_thread_summaries, amicus_thread_context, and
guided_study_questions so the UI surfaces can render enriched rows
without N+1 queries. New getters: getAmicusThreadContext,
getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session},
getLatestAmicusThreadIdForChapterContext,
getLatestStudyAmicusThreadIdForChapter,
getGuidedStudyQuestionForSession.
- db/userMutations.ts — upsertAmicusThreadContext,
upsertAmicusThreadSummary, reopenGuidedStudyQuestion.
deleteAmicusThread wrapped in a transaction and explicit
amicus_thread_context delete (belt-and-suspenders: with foreign_keys
now ON this is redundant but doesn't hurt; without it on past
runs, it's the only path that cleans up). resetToNewUser extended
to include amicus_thread_context.
- Existing createAmicusThread uses serializeAmicusChapterRef for
consistent storage format.
### Tests
- services/amicus/__tests__/context, trust, threadIntelligence,
studyActions, studyLaunch — all new. deepLink test extended.
- db/amicus/mutations + queries tests extended to cover the new
thread-context surface.
- utils/routeContext.test extended for the new helper.
- db/userDatabaseV3 test extended for migrations 22/23.
## Tier 2 fix included
PRAGMA foreign_keys = ON on every user-db open. Turns the FK
references in migrations 22/23 (and every prior migration that has
declared FKs) into actual runtime constraints. Flagged during the
senior review as a pre-existing latent issue this branch was
amplifying; fixing it alongside makes the new schema honest.
## Out of scope (moves to PR 3)
- All UI components, hooks, screens, and navigation types
- StudySessionScreen changes (preserves #1654 bounce-back fix)
- MyStudyScreen changes (deferred per #1652 re-plan)
## Risks
- **FK enforcement on existing DBs.** If earlier bugs wrote orphan
rows that violate a declared FK, those rows stay (SQLite doesn't
retroactively scan), but any NEW operation that tries to reference
the missing parent will start failing. Low risk in practice — our
existing migrations declare few FKs and those reference well-
populated tables (user_id, thread_id, session_id). Worth a
simulator smoke pass on a user.db that has real-world usage.
## Rollback
Revert the single merge. Migrations 22 and 23 stay in the sqlite_master
table — SQLite doesn't run migrations in reverse — but the code
that reads/writes those tables is gone, so the tables become inert.
New thread-context/summary rows stop being written; existing ones
orphan harmlessly. The FK pragma goes back to off. No user data loss.
Stacks with PR 1 (independent, can merge in either order) and PR 3
(depends on this).
@github-actions

Copy link
Copy Markdown

⚠️Tests: Could not parse results

…to master
Two CI failures on PR 2:
1. deletePreference regression — the codex branch removed this function
from userMutations.ts but left the caller in settingsStore.ts intact.
When I pulled userMutations wholesale into PR 2, the removal came
along, breaking tsc in settingsStore at line 16 (missing import) and
cascading to line 179 (implicit-any on the now-typeless .catch(err)).
Restored from master.
2. dailyPrompt.ts imports getAmicusAuthToken from authToken.ts, which
lives in PR 1. PR 2 was built to stand alone, so the cross-PR import
can't resolve. Reverted dailyPrompt.ts to master's version. The auth
integration for dailyPrompt can land with PR 3 or as a follow-up
once both PR 1 and PR 2 merge.
…dation
# Conflicts:
#	app/src/db/userMutations.ts
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3670❌ 03670
Suites✅ 498❌ 0498

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 91.3s

@CraigBuckmaster
CraigBuckmaster merged commit 5159ae0 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the feat/amicus-v2-foundation branch April 24, 2026 21:04
Sign up for freeto 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

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3) - #1660

Merged
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation
Apr 24, 2026
Merged

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3)#1660
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Part 2 of 3 — salvaging codex/amicus-auth-access-hardening

Foundation layer for the Amicus v2 work split out of codex/amicus-auth-access-hardening. No UI changes — every touched component is either a DB schema migration, a pure-function service, a type definition, or a test.

Stacks with:

Why this is its own PR

The codex branch shipped these foundation pieces intermingled with 10 UI surfaces in one 58-file blob. Splitting them out gives us:

  • A reviewable DB change surface (2 migrations + a PRAGMA change) evaluated on its own merits
  • The ability to roll back UI wiring without losing the data model
  • Clean test signal: foundation-layer tests aren't entangled with screen render tests

What's in

DB

  • Migration 22 — amicus_thread_summaries — one row per thread, stores summary_text, last_user_intent, updated_at. Feeds the thread list so rows show meaningful labels without re-parsing every message.
  • Migration 23 — amicus_thread_context — ties threads to guided-study sessions, specific steps, open questions, takeaways, key connections, and entry points. CHECK constraints on entry_point (fab|peek|thread|home_card|guided_study|my_study) and guided_step (scene|observe|explore|synthesize|review). FK references to amicus_threads (ON DELETE CASCADE) and guided_study_sessions / guided_study_questions (ON DELETE SET NULL). Two indexes tuned for recent-first lookups by (session, step) and by updated timestamp.
  • PRAGMA foreign_keys = ON on every user-db connection open. Tier 2 fix from the senior review. SQLite leaves FK checks off by default and the setting is per-connection. Without it, every REFERENCES ... ON DELETE ... in our schema is cosmetic. This makes them real runtime constraints. Runs outside any transaction, before migrations, so migration DDL is unaffected — only DML that violates a declared FK gets rejected, which is what we want.

Services (all new, all pure functions)

FileWhat it does
services/amicus/context.tsbuildAmicusContextEnvelope, chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef, formatAmicusContextLabel, prettyBookId. Shared envelope the Worker proxy receives + client-side formatting.
services/amicus/trust.tssummarizeAmicusTrust reduces citations into source labels and stance; formatTrustStanceLabel.
services/amicus/threadIntelligence.tsderiveThreadIntelligence derives title/summary/intent from first user query + guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
services/amicus/studyActions.tsbuildStudyActionSeeds enumerates one-tap actions for chapter-aware Amicus surfaces (explain_context, investigate_question, interpretive_tensions, refine_takeaway, trace_connections).
services/amicus/studyLaunch.tslaunchAmicusStudyThread resolves the right thread to open (dedup by question > session/step > chapter+entrypoint) and navigates. promotePeekToAmicusThread hydrates a peek session into a full thread.
services/amicus/deepLink.tsformatChapterRef helper extended for the new envelope shape.
services/amicus/dailyPrompt.tsMinor signature update to accept the enriched context.
services/amicus/index.tsBarrel exports all of the above.

Types

  • types/user.tsAmicusThread extended with summary_text, last_user_intent, guided_step, open_question_id, guided_question_status, takeaway, key_connection (all hydrated by a single LEFT JOIN'd query). New AmicusThreadContextRecord and AmicusDraftMessage types.

Queries + mutations

  • db/userQueries.tslistAmicusThreads / getAmicusThread now LEFT JOIN amicus_thread_summaries, amicus_thread_context, and guided_study_questions so the UI can render enriched rows without N+1. New getters: getAmicusThreadContext, getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session}, getLatestAmicusThreadIdForChapterContext, getLatestStudyAmicusThreadIdForChapter, getGuidedStudyQuestionForSession.
  • db/userMutations.tsupsertAmicusThreadContext and upsertAmicusThreadSummary (proper ON CONFLICT DO UPDATE SET … = excluded.*). reopenGuidedStudyQuestion. deleteAmicusThread wrapped in a transaction with explicit amicus_thread_context delete (redundant with FK cascade now ON, but defensive against DBs that predated this PR's pragma). resetToNewUser extended. createAmicusThread uses serializeAmicusChapterRef.
  • Not in this PR: the emitAmicusUsageChanged calls in incrementAmicusUsage / clearAllAmicusData — those belong with the subscription in PR feat(amicus): harden auth access with RC init wiring (1/3) #1658 and are stripped here to keep the PRs independent.

Tests

  • New: services/amicus/__tests__/ for context, trust, threadIntelligence, studyActions, studyLaunch
  • Extended: deepLink, db/amicus/mutations + queries, utils/routeContext, db/userDatabaseV3 (migrations 22/23)

Tier 2 fix included

PRAGMA foreign_keys = ON — turns the FK references in migrations 22/23 (and all prior migrations that have declared FKs) into actual runtime constraints. Flagged during the senior review as a pre-existing latent issue this branch was amplifying; fixing it here makes the new schema honest.

Risks

FK enforcement on existing DBs. If earlier bugs wrote orphan rows that violate a declared FK, those rows stay (SQLite doesn't retroactively scan) — but any new operation that tries to reference a missing parent will start failing. Low risk in practice: existing migrations declare few FKs and those reference well-populated tables. Worth a simulator smoke pass on a user.db that has real-world usage before cutting a release.

Out of scope (moves to PR 3)

Test plan

  • tsc --noEmit + jest via CI
  • Manual smoke (rentamac iOS simulator):
    1. First launch — new user-db gets migrations 22/23 applied; sqlite_master shows both tables + both indexes
    2. PRAGMA foreign_keys returns 1 on a fresh connection
    3. Manually insert a bad FK (e.g. INSERT INTO amicus_thread_context(thread_id, entry_point) VALUES ('nope', 'fab')) — expect SQLITE_CONSTRAINT
    4. Delete an amicus_threads row that has a matching amicus_thread_context — verify CASCADE fires (context row gone)
    5. Existing app surfaces (notes, bookmarks, reading progress) continue to work — smoke each of the main screens

Rollback

Single-commit revert. Migrations 22 and 23 stay in sqlite_master (SQLite doesn't run migrations in reverse), but the code that reads/writes them is gone so the tables become inert. New thread-context/summary rows stop being written; existing ones orphan harmlessly. FK pragma goes back to off on the next release. No user data loss.

…ment
Part 2 of splitting codex/amicus-auth-access-hardening. This PR lands
the non-UI foundation for the Amicus v2 work: DB schema, type
definitions, new pure-function services, and the accompanying tests.
## What this does
### DB layer
- **Migration 22** — amicus_thread_summaries. Stores derived title/
summary and last-user-intent per thread so the thread list can show
meaningful context without re-reading every message.
- **Migration 23** — amicus_thread_context. Ties threads to guided-
study sessions, specific steps, open questions, takeaways, and
entry-point (FAB / peek / thread / home_card / guided_study /
my_study). CHECK constraints on enum fields, FK references to
amicus_threads (CASCADE) and guided_study_sessions / questions
(SET NULL). Two indexes: (session_id, step, updated_at DESC) and
(updated_at DESC) for recent-first lookups.
- **PRAGMA foreign_keys = ON** on every user-db connection open
(Tier 2 fix). SQLite leaves FK checks off by default and the
setting is per-connection — without this, every REFERENCES clause
in our schema is cosmetic. This turns them into real runtime
constraints. DDL is unaffected; only DML that would violate a
declared FK gets rejected.
### Services (pure functions, no UI)
- services/amicus/context.ts — buildAmicusContextEnvelope,
chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef,
formatAmicusContextLabel, prettyBookId. Shared envelope the proxy
receives plus client-side formatting.
- services/amicus/trust.ts — summarizeAmicusTrust reduces citations
into a trust summary the TrustFooter will render.
- services/amicus/threadIntelligence.ts — deriveThreadIntelligence
builds title/summary/lastUserIntent from the first user query +
guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
- services/amicus/studyActions.ts — buildStudyActionSeeds enumerates
one-tap actions shown in Amicus surfaces when a chapter is in view
(explain_context, investigate_question, interpretive_tensions,
refine_takeaway, trace_connections).
- services/amicus/studyLaunch.ts — launchAmicusStudyThread resolves
the right thread to open (dedup by question > session/step >
chapter+entrypoint) and navigates. promotePeekToAmicusThread
hydrates a peek session into a full thread with messages replayed.
- services/amicus/deepLink.ts — formatChapterRef helper extended.
- services/amicus/dailyPrompt.ts — minor signature update.
- services/amicus/index.ts — barrel exports all of the above.
### Types + queries + mutations
- types/user.ts — AmicusThread now carries summary/intent/guided-step
fields; new AmicusThreadContextRecord and AmicusDraftMessage types.
- db/userQueries.ts — list/get thread queries LEFT JOIN
amicus_thread_summaries, amicus_thread_context, and
guided_study_questions so the UI surfaces can render enriched rows
without N+1 queries. New getters: getAmicusThreadContext,
getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session},
getLatestAmicusThreadIdForChapterContext,
getLatestStudyAmicusThreadIdForChapter,
getGuidedStudyQuestionForSession.
- db/userMutations.ts — upsertAmicusThreadContext,
upsertAmicusThreadSummary, reopenGuidedStudyQuestion.
deleteAmicusThread wrapped in a transaction and explicit
amicus_thread_context delete (belt-and-suspenders: with foreign_keys
now ON this is redundant but doesn't hurt; without it on past
runs, it's the only path that cleans up). resetToNewUser extended
to include amicus_thread_context.
- Existing createAmicusThread uses serializeAmicusChapterRef for
consistent storage format.
### Tests
- services/amicus/__tests__/context, trust, threadIntelligence,
studyActions, studyLaunch — all new. deepLink test extended.
- db/amicus/mutations + queries tests extended to cover the new
thread-context surface.
- utils/routeContext.test extended for the new helper.
- db/userDatabaseV3 test extended for migrations 22/23.
## Tier 2 fix included
PRAGMA foreign_keys = ON on every user-db open. Turns the FK
references in migrations 22/23 (and every prior migration that has
declared FKs) into actual runtime constraints. Flagged during the
senior review as a pre-existing latent issue this branch was
amplifying; fixing it alongside makes the new schema honest.
## Out of scope (moves to PR 3)
- All UI components, hooks, screens, and navigation types
- StudySessionScreen changes (preserves #1654 bounce-back fix)
- MyStudyScreen changes (deferred per #1652 re-plan)
## Risks
- **FK enforcement on existing DBs.** If earlier bugs wrote orphan
rows that violate a declared FK, those rows stay (SQLite doesn't
retroactively scan), but any NEW operation that tries to reference
the missing parent will start failing. Low risk in practice — our
existing migrations declare few FKs and those reference well-
populated tables (user_id, thread_id, session_id). Worth a
simulator smoke pass on a user.db that has real-world usage.
## Rollback
Revert the single merge. Migrations 22 and 23 stay in the sqlite_master
table — SQLite doesn't run migrations in reverse — but the code
that reads/writes those tables is gone, so the tables become inert.
New thread-context/summary rows stop being written; existing ones
orphan harmlessly. The FK pragma goes back to off. No user data loss.
Stacks with PR 1 (independent, can merge in either order) and PR 3
(depends on this).
@github-actions

Copy link
Copy Markdown

⚠️Tests: Could not parse results

…to master
Two CI failures on PR 2:
1. deletePreference regression — the codex branch removed this function
from userMutations.ts but left the caller in settingsStore.ts intact.
When I pulled userMutations wholesale into PR 2, the removal came
along, breaking tsc in settingsStore at line 16 (missing import) and
cascading to line 179 (implicit-any on the now-typeless .catch(err)).
Restored from master.
2. dailyPrompt.ts imports getAmicusAuthToken from authToken.ts, which
lives in PR 1. PR 2 was built to stand alone, so the cross-PR import
can't resolve. Reverted dailyPrompt.ts to master's version. The auth
integration for dailyPrompt can land with PR 3 or as a follow-up
once both PR 1 and PR 2 merge.
…dation
# Conflicts:
#	app/src/db/userMutations.ts
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3670❌ 03670
Suites✅ 498❌ 0498

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 91.3s

@CraigBuckmaster
CraigBuckmaster merged commit 5159ae0 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the feat/amicus-v2-foundation branch April 24, 2026 21:04
Sign up for freeto 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

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3) - #1660

Merged
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation
Apr 24, 2026
Merged

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3)#1660
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Part 2 of 3 — salvaging codex/amicus-auth-access-hardening

Foundation layer for the Amicus v2 work split out of codex/amicus-auth-access-hardening. No UI changes — every touched component is either a DB schema migration, a pure-function service, a type definition, or a test.

Stacks with:

Why this is its own PR

The codex branch shipped these foundation pieces intermingled with 10 UI surfaces in one 58-file blob. Splitting them out gives us:

  • A reviewable DB change surface (2 migrations + a PRAGMA change) evaluated on its own merits
  • The ability to roll back UI wiring without losing the data model
  • Clean test signal: foundation-layer tests aren't entangled with screen render tests

What's in

DB

  • Migration 22 — amicus_thread_summaries — one row per thread, stores summary_text, last_user_intent, updated_at. Feeds the thread list so rows show meaningful labels without re-parsing every message.
  • Migration 23 — amicus_thread_context — ties threads to guided-study sessions, specific steps, open questions, takeaways, key connections, and entry points. CHECK constraints on entry_point (fab|peek|thread|home_card|guided_study|my_study) and guided_step (scene|observe|explore|synthesize|review). FK references to amicus_threads (ON DELETE CASCADE) and guided_study_sessions / guided_study_questions (ON DELETE SET NULL). Two indexes tuned for recent-first lookups by (session, step) and by updated timestamp.
  • PRAGMA foreign_keys = ON on every user-db connection open. Tier 2 fix from the senior review. SQLite leaves FK checks off by default and the setting is per-connection. Without it, every REFERENCES ... ON DELETE ... in our schema is cosmetic. This makes them real runtime constraints. Runs outside any transaction, before migrations, so migration DDL is unaffected — only DML that violates a declared FK gets rejected, which is what we want.

Services (all new, all pure functions)

FileWhat it does
services/amicus/context.tsbuildAmicusContextEnvelope, chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef, formatAmicusContextLabel, prettyBookId. Shared envelope the Worker proxy receives + client-side formatting.
services/amicus/trust.tssummarizeAmicusTrust reduces citations into source labels and stance; formatTrustStanceLabel.
services/amicus/threadIntelligence.tsderiveThreadIntelligence derives title/summary/intent from first user query + guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
services/amicus/studyActions.tsbuildStudyActionSeeds enumerates one-tap actions for chapter-aware Amicus surfaces (explain_context, investigate_question, interpretive_tensions, refine_takeaway, trace_connections).
services/amicus/studyLaunch.tslaunchAmicusStudyThread resolves the right thread to open (dedup by question > session/step > chapter+entrypoint) and navigates. promotePeekToAmicusThread hydrates a peek session into a full thread.
services/amicus/deepLink.tsformatChapterRef helper extended for the new envelope shape.
services/amicus/dailyPrompt.tsMinor signature update to accept the enriched context.
services/amicus/index.tsBarrel exports all of the above.

Types

  • types/user.tsAmicusThread extended with summary_text, last_user_intent, guided_step, open_question_id, guided_question_status, takeaway, key_connection (all hydrated by a single LEFT JOIN'd query). New AmicusThreadContextRecord and AmicusDraftMessage types.

Queries + mutations

  • db/userQueries.tslistAmicusThreads / getAmicusThread now LEFT JOIN amicus_thread_summaries, amicus_thread_context, and guided_study_questions so the UI can render enriched rows without N+1. New getters: getAmicusThreadContext, getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session}, getLatestAmicusThreadIdForChapterContext, getLatestStudyAmicusThreadIdForChapter, getGuidedStudyQuestionForSession.
  • db/userMutations.tsupsertAmicusThreadContext and upsertAmicusThreadSummary (proper ON CONFLICT DO UPDATE SET … = excluded.*). reopenGuidedStudyQuestion. deleteAmicusThread wrapped in a transaction with explicit amicus_thread_context delete (redundant with FK cascade now ON, but defensive against DBs that predated this PR's pragma). resetToNewUser extended. createAmicusThread uses serializeAmicusChapterRef.
  • Not in this PR: the emitAmicusUsageChanged calls in incrementAmicusUsage / clearAllAmicusData — those belong with the subscription in PR feat(amicus): harden auth access with RC init wiring (1/3) #1658 and are stripped here to keep the PRs independent.

Tests

  • New: services/amicus/__tests__/ for context, trust, threadIntelligence, studyActions, studyLaunch
  • Extended: deepLink, db/amicus/mutations + queries, utils/routeContext, db/userDatabaseV3 (migrations 22/23)

Tier 2 fix included

PRAGMA foreign_keys = ON — turns the FK references in migrations 22/23 (and all prior migrations that have declared FKs) into actual runtime constraints. Flagged during the senior review as a pre-existing latent issue this branch was amplifying; fixing it here makes the new schema honest.

Risks

FK enforcement on existing DBs. If earlier bugs wrote orphan rows that violate a declared FK, those rows stay (SQLite doesn't retroactively scan) — but any new operation that tries to reference a missing parent will start failing. Low risk in practice: existing migrations declare few FKs and those reference well-populated tables. Worth a simulator smoke pass on a user.db that has real-world usage before cutting a release.

Out of scope (moves to PR 3)

Test plan

  • tsc --noEmit + jest via CI
  • Manual smoke (rentamac iOS simulator):
    1. First launch — new user-db gets migrations 22/23 applied; sqlite_master shows both tables + both indexes
    2. PRAGMA foreign_keys returns 1 on a fresh connection
    3. Manually insert a bad FK (e.g. INSERT INTO amicus_thread_context(thread_id, entry_point) VALUES ('nope', 'fab')) — expect SQLITE_CONSTRAINT
    4. Delete an amicus_threads row that has a matching amicus_thread_context — verify CASCADE fires (context row gone)
    5. Existing app surfaces (notes, bookmarks, reading progress) continue to work — smoke each of the main screens

Rollback

Single-commit revert. Migrations 22 and 23 stay in sqlite_master (SQLite doesn't run migrations in reverse), but the code that reads/writes them is gone so the tables become inert. New thread-context/summary rows stop being written; existing ones orphan harmlessly. FK pragma goes back to off on the next release. No user data loss.

…ment
Part 2 of splitting codex/amicus-auth-access-hardening. This PR lands
the non-UI foundation for the Amicus v2 work: DB schema, type
definitions, new pure-function services, and the accompanying tests.
## What this does
### DB layer
- **Migration 22** — amicus_thread_summaries. Stores derived title/
summary and last-user-intent per thread so the thread list can show
meaningful context without re-reading every message.
- **Migration 23** — amicus_thread_context. Ties threads to guided-
study sessions, specific steps, open questions, takeaways, and
entry-point (FAB / peek / thread / home_card / guided_study /
my_study). CHECK constraints on enum fields, FK references to
amicus_threads (CASCADE) and guided_study_sessions / questions
(SET NULL). Two indexes: (session_id, step, updated_at DESC) and
(updated_at DESC) for recent-first lookups.
- **PRAGMA foreign_keys = ON** on every user-db connection open
(Tier 2 fix). SQLite leaves FK checks off by default and the
setting is per-connection — without this, every REFERENCES clause
in our schema is cosmetic. This turns them into real runtime
constraints. DDL is unaffected; only DML that would violate a
declared FK gets rejected.
### Services (pure functions, no UI)
- services/amicus/context.ts — buildAmicusContextEnvelope,
chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef,
formatAmicusContextLabel, prettyBookId. Shared envelope the proxy
receives plus client-side formatting.
- services/amicus/trust.ts — summarizeAmicusTrust reduces citations
into a trust summary the TrustFooter will render.
- services/amicus/threadIntelligence.ts — deriveThreadIntelligence
builds title/summary/lastUserIntent from the first user query +
guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
- services/amicus/studyActions.ts — buildStudyActionSeeds enumerates
one-tap actions shown in Amicus surfaces when a chapter is in view
(explain_context, investigate_question, interpretive_tensions,
refine_takeaway, trace_connections).
- services/amicus/studyLaunch.ts — launchAmicusStudyThread resolves
the right thread to open (dedup by question > session/step >
chapter+entrypoint) and navigates. promotePeekToAmicusThread
hydrates a peek session into a full thread with messages replayed.
- services/amicus/deepLink.ts — formatChapterRef helper extended.
- services/amicus/dailyPrompt.ts — minor signature update.
- services/amicus/index.ts — barrel exports all of the above.
### Types + queries + mutations
- types/user.ts — AmicusThread now carries summary/intent/guided-step
fields; new AmicusThreadContextRecord and AmicusDraftMessage types.
- db/userQueries.ts — list/get thread queries LEFT JOIN
amicus_thread_summaries, amicus_thread_context, and
guided_study_questions so the UI surfaces can render enriched rows
without N+1 queries. New getters: getAmicusThreadContext,
getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session},
getLatestAmicusThreadIdForChapterContext,
getLatestStudyAmicusThreadIdForChapter,
getGuidedStudyQuestionForSession.
- db/userMutations.ts — upsertAmicusThreadContext,
upsertAmicusThreadSummary, reopenGuidedStudyQuestion.
deleteAmicusThread wrapped in a transaction and explicit
amicus_thread_context delete (belt-and-suspenders: with foreign_keys
now ON this is redundant but doesn't hurt; without it on past
runs, it's the only path that cleans up). resetToNewUser extended
to include amicus_thread_context.
- Existing createAmicusThread uses serializeAmicusChapterRef for
consistent storage format.
### Tests
- services/amicus/__tests__/context, trust, threadIntelligence,
studyActions, studyLaunch — all new. deepLink test extended.
- db/amicus/mutations + queries tests extended to cover the new
thread-context surface.
- utils/routeContext.test extended for the new helper.
- db/userDatabaseV3 test extended for migrations 22/23.
## Tier 2 fix included
PRAGMA foreign_keys = ON on every user-db open. Turns the FK
references in migrations 22/23 (and every prior migration that has
declared FKs) into actual runtime constraints. Flagged during the
senior review as a pre-existing latent issue this branch was
amplifying; fixing it alongside makes the new schema honest.
## Out of scope (moves to PR 3)
- All UI components, hooks, screens, and navigation types
- StudySessionScreen changes (preserves #1654 bounce-back fix)
- MyStudyScreen changes (deferred per #1652 re-plan)
## Risks
- **FK enforcement on existing DBs.** If earlier bugs wrote orphan
rows that violate a declared FK, those rows stay (SQLite doesn't
retroactively scan), but any NEW operation that tries to reference
the missing parent will start failing. Low risk in practice — our
existing migrations declare few FKs and those reference well-
populated tables (user_id, thread_id, session_id). Worth a
simulator smoke pass on a user.db that has real-world usage.
## Rollback
Revert the single merge. Migrations 22 and 23 stay in the sqlite_master
table — SQLite doesn't run migrations in reverse — but the code
that reads/writes those tables is gone, so the tables become inert.
New thread-context/summary rows stop being written; existing ones
orphan harmlessly. The FK pragma goes back to off. No user data loss.
Stacks with PR 1 (independent, can merge in either order) and PR 3
(depends on this).
@github-actions

Copy link
Copy Markdown

⚠️Tests: Could not parse results

…to master
Two CI failures on PR 2:
1. deletePreference regression — the codex branch removed this function
from userMutations.ts but left the caller in settingsStore.ts intact.
When I pulled userMutations wholesale into PR 2, the removal came
along, breaking tsc in settingsStore at line 16 (missing import) and
cascading to line 179 (implicit-any on the now-typeless .catch(err)).
Restored from master.
2. dailyPrompt.ts imports getAmicusAuthToken from authToken.ts, which
lives in PR 1. PR 2 was built to stand alone, so the cross-PR import
can't resolve. Reverted dailyPrompt.ts to master's version. The auth
integration for dailyPrompt can land with PR 3 or as a follow-up
once both PR 1 and PR 2 merge.
…dation
# Conflicts:
#	app/src/db/userMutations.ts
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3670❌ 03670
Suites✅ 498❌ 0498

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 91.3s

@CraigBuckmaster
CraigBuckmaster merged commit 5159ae0 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the feat/amicus-v2-foundation branch April 24, 2026 21:04
Sign up for freeto 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

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3) - #1660

Merged
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation
Apr 24, 2026
Merged

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3)#1660
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Part 2 of 3 — salvaging codex/amicus-auth-access-hardening

Foundation layer for the Amicus v2 work split out of codex/amicus-auth-access-hardening. No UI changes — every touched component is either a DB schema migration, a pure-function service, a type definition, or a test.

Stacks with:

Why this is its own PR

The codex branch shipped these foundation pieces intermingled with 10 UI surfaces in one 58-file blob. Splitting them out gives us:

  • A reviewable DB change surface (2 migrations + a PRAGMA change) evaluated on its own merits
  • The ability to roll back UI wiring without losing the data model
  • Clean test signal: foundation-layer tests aren't entangled with screen render tests

What's in

DB

  • Migration 22 — amicus_thread_summaries — one row per thread, stores summary_text, last_user_intent, updated_at. Feeds the thread list so rows show meaningful labels without re-parsing every message.
  • Migration 23 — amicus_thread_context — ties threads to guided-study sessions, specific steps, open questions, takeaways, key connections, and entry points. CHECK constraints on entry_point (fab|peek|thread|home_card|guided_study|my_study) and guided_step (scene|observe|explore|synthesize|review). FK references to amicus_threads (ON DELETE CASCADE) and guided_study_sessions / guided_study_questions (ON DELETE SET NULL). Two indexes tuned for recent-first lookups by (session, step) and by updated timestamp.
  • PRAGMA foreign_keys = ON on every user-db connection open. Tier 2 fix from the senior review. SQLite leaves FK checks off by default and the setting is per-connection. Without it, every REFERENCES ... ON DELETE ... in our schema is cosmetic. This makes them real runtime constraints. Runs outside any transaction, before migrations, so migration DDL is unaffected — only DML that violates a declared FK gets rejected, which is what we want.

Services (all new, all pure functions)

FileWhat it does
services/amicus/context.tsbuildAmicusContextEnvelope, chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef, formatAmicusContextLabel, prettyBookId. Shared envelope the Worker proxy receives + client-side formatting.
services/amicus/trust.tssummarizeAmicusTrust reduces citations into source labels and stance; formatTrustStanceLabel.
services/amicus/threadIntelligence.tsderiveThreadIntelligence derives title/summary/intent from first user query + guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
services/amicus/studyActions.tsbuildStudyActionSeeds enumerates one-tap actions for chapter-aware Amicus surfaces (explain_context, investigate_question, interpretive_tensions, refine_takeaway, trace_connections).
services/amicus/studyLaunch.tslaunchAmicusStudyThread resolves the right thread to open (dedup by question > session/step > chapter+entrypoint) and navigates. promotePeekToAmicusThread hydrates a peek session into a full thread.
services/amicus/deepLink.tsformatChapterRef helper extended for the new envelope shape.
services/amicus/dailyPrompt.tsMinor signature update to accept the enriched context.
services/amicus/index.tsBarrel exports all of the above.

Types

  • types/user.tsAmicusThread extended with summary_text, last_user_intent, guided_step, open_question_id, guided_question_status, takeaway, key_connection (all hydrated by a single LEFT JOIN'd query). New AmicusThreadContextRecord and AmicusDraftMessage types.

Queries + mutations

  • db/userQueries.tslistAmicusThreads / getAmicusThread now LEFT JOIN amicus_thread_summaries, amicus_thread_context, and guided_study_questions so the UI can render enriched rows without N+1. New getters: getAmicusThreadContext, getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session}, getLatestAmicusThreadIdForChapterContext, getLatestStudyAmicusThreadIdForChapter, getGuidedStudyQuestionForSession.
  • db/userMutations.tsupsertAmicusThreadContext and upsertAmicusThreadSummary (proper ON CONFLICT DO UPDATE SET … = excluded.*). reopenGuidedStudyQuestion. deleteAmicusThread wrapped in a transaction with explicit amicus_thread_context delete (redundant with FK cascade now ON, but defensive against DBs that predated this PR's pragma). resetToNewUser extended. createAmicusThread uses serializeAmicusChapterRef.
  • Not in this PR: the emitAmicusUsageChanged calls in incrementAmicusUsage / clearAllAmicusData — those belong with the subscription in PR feat(amicus): harden auth access with RC init wiring (1/3) #1658 and are stripped here to keep the PRs independent.

Tests

  • New: services/amicus/__tests__/ for context, trust, threadIntelligence, studyActions, studyLaunch
  • Extended: deepLink, db/amicus/mutations + queries, utils/routeContext, db/userDatabaseV3 (migrations 22/23)

Tier 2 fix included

PRAGMA foreign_keys = ON — turns the FK references in migrations 22/23 (and all prior migrations that have declared FKs) into actual runtime constraints. Flagged during the senior review as a pre-existing latent issue this branch was amplifying; fixing it here makes the new schema honest.

Risks

FK enforcement on existing DBs. If earlier bugs wrote orphan rows that violate a declared FK, those rows stay (SQLite doesn't retroactively scan) — but any new operation that tries to reference a missing parent will start failing. Low risk in practice: existing migrations declare few FKs and those reference well-populated tables. Worth a simulator smoke pass on a user.db that has real-world usage before cutting a release.

Out of scope (moves to PR 3)

Test plan

  • tsc --noEmit + jest via CI
  • Manual smoke (rentamac iOS simulator):
    1. First launch — new user-db gets migrations 22/23 applied; sqlite_master shows both tables + both indexes
    2. PRAGMA foreign_keys returns 1 on a fresh connection
    3. Manually insert a bad FK (e.g. INSERT INTO amicus_thread_context(thread_id, entry_point) VALUES ('nope', 'fab')) — expect SQLITE_CONSTRAINT
    4. Delete an amicus_threads row that has a matching amicus_thread_context — verify CASCADE fires (context row gone)
    5. Existing app surfaces (notes, bookmarks, reading progress) continue to work — smoke each of the main screens

Rollback

Single-commit revert. Migrations 22 and 23 stay in sqlite_master (SQLite doesn't run migrations in reverse), but the code that reads/writes them is gone so the tables become inert. New thread-context/summary rows stop being written; existing ones orphan harmlessly. FK pragma goes back to off on the next release. No user data loss.

…ment
Part 2 of splitting codex/amicus-auth-access-hardening. This PR lands
the non-UI foundation for the Amicus v2 work: DB schema, type
definitions, new pure-function services, and the accompanying tests.
## What this does
### DB layer
- **Migration 22** — amicus_thread_summaries. Stores derived title/
summary and last-user-intent per thread so the thread list can show
meaningful context without re-reading every message.
- **Migration 23** — amicus_thread_context. Ties threads to guided-
study sessions, specific steps, open questions, takeaways, and
entry-point (FAB / peek / thread / home_card / guided_study /
my_study). CHECK constraints on enum fields, FK references to
amicus_threads (CASCADE) and guided_study_sessions / questions
(SET NULL). Two indexes: (session_id, step, updated_at DESC) and
(updated_at DESC) for recent-first lookups.
- **PRAGMA foreign_keys = ON** on every user-db connection open
(Tier 2 fix). SQLite leaves FK checks off by default and the
setting is per-connection — without this, every REFERENCES clause
in our schema is cosmetic. This turns them into real runtime
constraints. DDL is unaffected; only DML that would violate a
declared FK gets rejected.
### Services (pure functions, no UI)
- services/amicus/context.ts — buildAmicusContextEnvelope,
chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef,
formatAmicusContextLabel, prettyBookId. Shared envelope the proxy
receives plus client-side formatting.
- services/amicus/trust.ts — summarizeAmicusTrust reduces citations
into a trust summary the TrustFooter will render.
- services/amicus/threadIntelligence.ts — deriveThreadIntelligence
builds title/summary/lastUserIntent from the first user query +
guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
- services/amicus/studyActions.ts — buildStudyActionSeeds enumerates
one-tap actions shown in Amicus surfaces when a chapter is in view
(explain_context, investigate_question, interpretive_tensions,
refine_takeaway, trace_connections).
- services/amicus/studyLaunch.ts — launchAmicusStudyThread resolves
the right thread to open (dedup by question > session/step >
chapter+entrypoint) and navigates. promotePeekToAmicusThread
hydrates a peek session into a full thread with messages replayed.
- services/amicus/deepLink.ts — formatChapterRef helper extended.
- services/amicus/dailyPrompt.ts — minor signature update.
- services/amicus/index.ts — barrel exports all of the above.
### Types + queries + mutations
- types/user.ts — AmicusThread now carries summary/intent/guided-step
fields; new AmicusThreadContextRecord and AmicusDraftMessage types.
- db/userQueries.ts — list/get thread queries LEFT JOIN
amicus_thread_summaries, amicus_thread_context, and
guided_study_questions so the UI surfaces can render enriched rows
without N+1 queries. New getters: getAmicusThreadContext,
getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session},
getLatestAmicusThreadIdForChapterContext,
getLatestStudyAmicusThreadIdForChapter,
getGuidedStudyQuestionForSession.
- db/userMutations.ts — upsertAmicusThreadContext,
upsertAmicusThreadSummary, reopenGuidedStudyQuestion.
deleteAmicusThread wrapped in a transaction and explicit
amicus_thread_context delete (belt-and-suspenders: with foreign_keys
now ON this is redundant but doesn't hurt; without it on past
runs, it's the only path that cleans up). resetToNewUser extended
to include amicus_thread_context.
- Existing createAmicusThread uses serializeAmicusChapterRef for
consistent storage format.
### Tests
- services/amicus/__tests__/context, trust, threadIntelligence,
studyActions, studyLaunch — all new. deepLink test extended.
- db/amicus/mutations + queries tests extended to cover the new
thread-context surface.
- utils/routeContext.test extended for the new helper.
- db/userDatabaseV3 test extended for migrations 22/23.
## Tier 2 fix included
PRAGMA foreign_keys = ON on every user-db open. Turns the FK
references in migrations 22/23 (and every prior migration that has
declared FKs) into actual runtime constraints. Flagged during the
senior review as a pre-existing latent issue this branch was
amplifying; fixing it alongside makes the new schema honest.
## Out of scope (moves to PR 3)
- All UI components, hooks, screens, and navigation types
- StudySessionScreen changes (preserves #1654 bounce-back fix)
- MyStudyScreen changes (deferred per #1652 re-plan)
## Risks
- **FK enforcement on existing DBs.** If earlier bugs wrote orphan
rows that violate a declared FK, those rows stay (SQLite doesn't
retroactively scan), but any NEW operation that tries to reference
the missing parent will start failing. Low risk in practice — our
existing migrations declare few FKs and those reference well-
populated tables (user_id, thread_id, session_id). Worth a
simulator smoke pass on a user.db that has real-world usage.
## Rollback
Revert the single merge. Migrations 22 and 23 stay in the sqlite_master
table — SQLite doesn't run migrations in reverse — but the code
that reads/writes those tables is gone, so the tables become inert.
New thread-context/summary rows stop being written; existing ones
orphan harmlessly. The FK pragma goes back to off. No user data loss.
Stacks with PR 1 (independent, can merge in either order) and PR 3
(depends on this).
@github-actions

Copy link
Copy Markdown

⚠️Tests: Could not parse results

…to master
Two CI failures on PR 2:
1. deletePreference regression — the codex branch removed this function
from userMutations.ts but left the caller in settingsStore.ts intact.
When I pulled userMutations wholesale into PR 2, the removal came
along, breaking tsc in settingsStore at line 16 (missing import) and
cascading to line 179 (implicit-any on the now-typeless .catch(err)).
Restored from master.
2. dailyPrompt.ts imports getAmicusAuthToken from authToken.ts, which
lives in PR 1. PR 2 was built to stand alone, so the cross-PR import
can't resolve. Reverted dailyPrompt.ts to master's version. The auth
integration for dailyPrompt can land with PR 3 or as a follow-up
once both PR 1 and PR 2 merge.
…dation
# Conflicts:
#	app/src/db/userMutations.ts
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3670❌ 03670
Suites✅ 498❌ 0498

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 91.3s

@CraigBuckmaster
CraigBuckmaster merged commit 5159ae0 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the feat/amicus-v2-foundation branch April 24, 2026 21:04
Sign up for freeto 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

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3) - #1660

Merged
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation
Apr 24, 2026
Merged

feat(amicus): v2 foundation — migrations, services, types, FK enforcement (2/3)#1660
CraigBuckmaster merged 3 commits into
masterfrom
feat/amicus-v2-foundation

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Part 2 of 3 — salvaging codex/amicus-auth-access-hardening

Foundation layer for the Amicus v2 work split out of codex/amicus-auth-access-hardening. No UI changes — every touched component is either a DB schema migration, a pure-function service, a type definition, or a test.

Stacks with:

Why this is its own PR

The codex branch shipped these foundation pieces intermingled with 10 UI surfaces in one 58-file blob. Splitting them out gives us:

  • A reviewable DB change surface (2 migrations + a PRAGMA change) evaluated on its own merits
  • The ability to roll back UI wiring without losing the data model
  • Clean test signal: foundation-layer tests aren't entangled with screen render tests

What's in

DB

  • Migration 22 — amicus_thread_summaries — one row per thread, stores summary_text, last_user_intent, updated_at. Feeds the thread list so rows show meaningful labels without re-parsing every message.
  • Migration 23 — amicus_thread_context — ties threads to guided-study sessions, specific steps, open questions, takeaways, key connections, and entry points. CHECK constraints on entry_point (fab|peek|thread|home_card|guided_study|my_study) and guided_step (scene|observe|explore|synthesize|review). FK references to amicus_threads (ON DELETE CASCADE) and guided_study_sessions / guided_study_questions (ON DELETE SET NULL). Two indexes tuned for recent-first lookups by (session, step) and by updated timestamp.
  • PRAGMA foreign_keys = ON on every user-db connection open. Tier 2 fix from the senior review. SQLite leaves FK checks off by default and the setting is per-connection. Without it, every REFERENCES ... ON DELETE ... in our schema is cosmetic. This makes them real runtime constraints. Runs outside any transaction, before migrations, so migration DDL is unaffected — only DML that violates a declared FK gets rejected, which is what we want.

Services (all new, all pure functions)

FileWhat it does
services/amicus/context.tsbuildAmicusContextEnvelope, chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef, formatAmicusContextLabel, prettyBookId. Shared envelope the Worker proxy receives + client-side formatting.
services/amicus/trust.tssummarizeAmicusTrust reduces citations into source labels and stance; formatTrustStanceLabel.
services/amicus/threadIntelligence.tsderiveThreadIntelligence derives title/summary/intent from first user query + guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
services/amicus/studyActions.tsbuildStudyActionSeeds enumerates one-tap actions for chapter-aware Amicus surfaces (explain_context, investigate_question, interpretive_tensions, refine_takeaway, trace_connections).
services/amicus/studyLaunch.tslaunchAmicusStudyThread resolves the right thread to open (dedup by question > session/step > chapter+entrypoint) and navigates. promotePeekToAmicusThread hydrates a peek session into a full thread.
services/amicus/deepLink.tsformatChapterRef helper extended for the new envelope shape.
services/amicus/dailyPrompt.tsMinor signature update to accept the enriched context.
services/amicus/index.tsBarrel exports all of the above.

Types

  • types/user.tsAmicusThread extended with summary_text, last_user_intent, guided_step, open_question_id, guided_question_status, takeaway, key_connection (all hydrated by a single LEFT JOIN'd query). New AmicusThreadContextRecord and AmicusDraftMessage types.

Queries + mutations

  • db/userQueries.tslistAmicusThreads / getAmicusThread now LEFT JOIN amicus_thread_summaries, amicus_thread_context, and guided_study_questions so the UI can render enriched rows without N+1. New getters: getAmicusThreadContext, getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session}, getLatestAmicusThreadIdForChapterContext, getLatestStudyAmicusThreadIdForChapter, getGuidedStudyQuestionForSession.
  • db/userMutations.tsupsertAmicusThreadContext and upsertAmicusThreadSummary (proper ON CONFLICT DO UPDATE SET … = excluded.*). reopenGuidedStudyQuestion. deleteAmicusThread wrapped in a transaction with explicit amicus_thread_context delete (redundant with FK cascade now ON, but defensive against DBs that predated this PR's pragma). resetToNewUser extended. createAmicusThread uses serializeAmicusChapterRef.
  • Not in this PR: the emitAmicusUsageChanged calls in incrementAmicusUsage / clearAllAmicusData — those belong with the subscription in PR feat(amicus): harden auth access with RC init wiring (1/3) #1658 and are stripped here to keep the PRs independent.

Tests

  • New: services/amicus/__tests__/ for context, trust, threadIntelligence, studyActions, studyLaunch
  • Extended: deepLink, db/amicus/mutations + queries, utils/routeContext, db/userDatabaseV3 (migrations 22/23)

Tier 2 fix included

PRAGMA foreign_keys = ON — turns the FK references in migrations 22/23 (and all prior migrations that have declared FKs) into actual runtime constraints. Flagged during the senior review as a pre-existing latent issue this branch was amplifying; fixing it here makes the new schema honest.

Risks

FK enforcement on existing DBs. If earlier bugs wrote orphan rows that violate a declared FK, those rows stay (SQLite doesn't retroactively scan) — but any new operation that tries to reference a missing parent will start failing. Low risk in practice: existing migrations declare few FKs and those reference well-populated tables. Worth a simulator smoke pass on a user.db that has real-world usage before cutting a release.

Out of scope (moves to PR 3)

Test plan

  • tsc --noEmit + jest via CI
  • Manual smoke (rentamac iOS simulator):
    1. First launch — new user-db gets migrations 22/23 applied; sqlite_master shows both tables + both indexes
    2. PRAGMA foreign_keys returns 1 on a fresh connection
    3. Manually insert a bad FK (e.g. INSERT INTO amicus_thread_context(thread_id, entry_point) VALUES ('nope', 'fab')) — expect SQLITE_CONSTRAINT
    4. Delete an amicus_threads row that has a matching amicus_thread_context — verify CASCADE fires (context row gone)
    5. Existing app surfaces (notes, bookmarks, reading progress) continue to work — smoke each of the main screens

Rollback

Single-commit revert. Migrations 22 and 23 stay in sqlite_master (SQLite doesn't run migrations in reverse), but the code that reads/writes them is gone so the tables become inert. New thread-context/summary rows stop being written; existing ones orphan harmlessly. FK pragma goes back to off on the next release. No user data loss.

…ment
Part 2 of splitting codex/amicus-auth-access-hardening. This PR lands
the non-UI foundation for the Amicus v2 work: DB schema, type
definitions, new pure-function services, and the accompanying tests.
## What this does
### DB layer
- **Migration 22** — amicus_thread_summaries. Stores derived title/
summary and last-user-intent per thread so the thread list can show
meaningful context without re-reading every message.
- **Migration 23** — amicus_thread_context. Ties threads to guided-
study sessions, specific steps, open questions, takeaways, and
entry-point (FAB / peek / thread / home_card / guided_study /
my_study). CHECK constraints on enum fields, FK references to
amicus_threads (CASCADE) and guided_study_sessions / questions
(SET NULL). Two indexes: (session_id, step, updated_at DESC) and
(updated_at DESC) for recent-first lookups.
- **PRAGMA foreign_keys = ON** on every user-db connection open
(Tier 2 fix). SQLite leaves FK checks off by default and the
setting is per-connection — without this, every REFERENCES clause
in our schema is cosmetic. This turns them into real runtime
constraints. DDL is unaffected; only DML that would violate a
declared FK gets rejected.
### Services (pure functions, no UI)
- services/amicus/context.ts — buildAmicusContextEnvelope,
chapterRefFromChipContext, normalizeChapterRef, serializeAmicusChapterRef,
formatAmicusContextLabel, prettyBookId. Shared envelope the proxy
receives plus client-side formatting.
- services/amicus/trust.ts — summarizeAmicusTrust reduces citations
into a trust summary the TrustFooter will render.
- services/amicus/threadIntelligence.ts — deriveThreadIntelligence
builds title/summary/lastUserIntent from the first user query +
guided-study context. shouldAutoRenameThread, summarizeLinkedQuestionState.
- services/amicus/studyActions.ts — buildStudyActionSeeds enumerates
one-tap actions shown in Amicus surfaces when a chapter is in view
(explain_context, investigate_question, interpretive_tensions,
refine_takeaway, trace_connections).
- services/amicus/studyLaunch.ts — launchAmicusStudyThread resolves
the right thread to open (dedup by question > session/step >
chapter+entrypoint) and navigates. promotePeekToAmicusThread
hydrates a peek session into a full thread with messages replayed.
- services/amicus/deepLink.ts — formatChapterRef helper extended.
- services/amicus/dailyPrompt.ts — minor signature update.
- services/amicus/index.ts — barrel exports all of the above.
### Types + queries + mutations
- types/user.ts — AmicusThread now carries summary/intent/guided-step
fields; new AmicusThreadContextRecord and AmicusDraftMessage types.
- db/userQueries.ts — list/get thread queries LEFT JOIN
amicus_thread_summaries, amicus_thread_context, and
guided_study_questions so the UI surfaces can render enriched rows
without N+1 queries. New getters: getAmicusThreadContext,
getLinkedGuidedStudyQuestionForThread, getAmicusThreadIdForGuided{Question,Session},
getLatestAmicusThreadIdForChapterContext,
getLatestStudyAmicusThreadIdForChapter,
getGuidedStudyQuestionForSession.
- db/userMutations.ts — upsertAmicusThreadContext,
upsertAmicusThreadSummary, reopenGuidedStudyQuestion.
deleteAmicusThread wrapped in a transaction and explicit
amicus_thread_context delete (belt-and-suspenders: with foreign_keys
now ON this is redundant but doesn't hurt; without it on past
runs, it's the only path that cleans up). resetToNewUser extended
to include amicus_thread_context.
- Existing createAmicusThread uses serializeAmicusChapterRef for
consistent storage format.
### Tests
- services/amicus/__tests__/context, trust, threadIntelligence,
studyActions, studyLaunch — all new. deepLink test extended.
- db/amicus/mutations + queries tests extended to cover the new
thread-context surface.
- utils/routeContext.test extended for the new helper.
- db/userDatabaseV3 test extended for migrations 22/23.
## Tier 2 fix included
PRAGMA foreign_keys = ON on every user-db open. Turns the FK
references in migrations 22/23 (and every prior migration that has
declared FKs) into actual runtime constraints. Flagged during the
senior review as a pre-existing latent issue this branch was
amplifying; fixing it alongside makes the new schema honest.
## Out of scope (moves to PR 3)
- All UI components, hooks, screens, and navigation types
- StudySessionScreen changes (preserves #1654 bounce-back fix)
- MyStudyScreen changes (deferred per #1652 re-plan)
## Risks
- **FK enforcement on existing DBs.** If earlier bugs wrote orphan
rows that violate a declared FK, those rows stay (SQLite doesn't
retroactively scan), but any NEW operation that tries to reference
the missing parent will start failing. Low risk in practice — our
existing migrations declare few FKs and those reference well-
populated tables (user_id, thread_id, session_id). Worth a
simulator smoke pass on a user.db that has real-world usage.
## Rollback
Revert the single merge. Migrations 22 and 23 stay in the sqlite_master
table — SQLite doesn't run migrations in reverse — but the code
that reads/writes those tables is gone, so the tables become inert.
New thread-context/summary rows stop being written; existing ones
orphan harmlessly. The FK pragma goes back to off. No user data loss.
Stacks with PR 1 (independent, can merge in either order) and PR 3
(depends on this).
@github-actions

Copy link
Copy Markdown

⚠️Tests: Could not parse results

…to master
Two CI failures on PR 2:
1. deletePreference regression — the codex branch removed this function
from userMutations.ts but left the caller in settingsStore.ts intact.
When I pulled userMutations wholesale into PR 2, the removal came
along, breaking tsc in settingsStore at line 16 (missing import) and
cascading to line 179 (implicit-any on the now-typeless .catch(err)).
Restored from master.
2. dailyPrompt.ts imports getAmicusAuthToken from authToken.ts, which
lives in PR 1. PR 2 was built to stand alone, so the cross-PR import
can't resolve. Reverted dailyPrompt.ts to master's version. The auth
integration for dailyPrompt can land with PR 3 or as a follow-up
once both PR 1 and PR 2 merge.
…dation
# Conflicts:
#	app/src/db/userMutations.ts
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3670❌ 03670
Suites✅ 498❌ 0498

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 91.3s

@CraigBuckmaster
CraigBuckmaster merged commit 5159ae0 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the feat/amicus-v2-foundation branch April 24, 2026 21:04
Sign up for freeto 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

@CraigBuckmaster@claude