diff --git a/docs/CODEBASE_MAP.md b/docs/CODEBASE_MAP.md index 32927ea..571e7b1 100644 --- a/docs/CODEBASE_MAP.md +++ b/docs/CODEBASE_MAP.md @@ -109,6 +109,8 @@ typography, shadows, and scaling; `ThemeContext` persists light/dark preference. - `data/whatsNew.ts`, `hooks/useWhatsNew.ts`, and `services/whatsNewStore.ts` control version announcements. Every release includes a matching one-time card focused on new features and user-visible improvements, per `RELEASING.md`. + `components/WhatsNewCard.tsx` scrolls long highlight lists independently of the + heading/dismissal controls so small screens can reach every item. - `src/types/index.ts` defines shared concept, progress, daily, history, and streak types. API payloads also have types near their service consumers. @@ -232,7 +234,7 @@ session pooler. Applied migrations must not be rewritten. findings as issues. `cleanup.yml` manages stale issues; Dependabot schedules dependency updates with Expo-managed version restrictions. The checked-in workflows do not include a general PR pytest job. -- `mobile/app.config.js` currently has app version `1.7.1` and native runtime +- `mobile/app.config.js` currently has app version `1.8.0` and native runtime `1.3.0`; `package.json`'s `1.0.0` is not the release-version authority. ## Documentation drift to remember diff --git a/docs/WORK_LOG.md b/docs/WORK_LOG.md index 99237f8..a630a05 100644 --- a/docs/WORK_LOG.md +++ b/docs/WORK_LOG.md @@ -7,39 +7,71 @@ claims as completed work. ## Current status -- Implemented and validated [#182](https://github.com/Coding-Moves/one-concept/issues/182) - on `codex/182-offline-saved-password` from refreshed `develop` (`58e9b1d`). - Added show/hide controls for sign-in/signup. Verified the existing full-lesson - cache across offline restart, and strengthened coverage of saved examples. - Published [PR #189](https://github.com/Coding-Moves/one-concept/pull/189) into - `develop`; phone rollout requires the next release. -- PR #188 is merged. The owner reports installing all three email templates and - enabling the notification in Supabase; actual inbox delivery remains unverified. - -- Published the three branded Supabase templates in non-draft - [PR #188](https://github.com/Coding-Moves/one-concept/pull/188) into `develop`, - from `a6e81f1` on `codex/branded-auth-email-templates`. GitHub reports the PR - mergeable with no conflicts; no CI checks are configured for this PR. -- Resend delivery preparation remains in [draft PR #187](https://github.com/Coding-Moves/one-concept/pull/187). - Its duplicate template files are removed in a follow-up commit. Original - histories are intact. Template installation is independent of a domain purchase - or app release and still requires pasting HTML into Supabase. -- No SMTP settings, live templates, notifications, or production release were - changed. Real inbox rendering and delivery remain unverified. - -- Implemented and validated [#151](https://github.com/Coding-Moves/one-concept/issues/151), - shared daily Gemini generation budget, on `codex/151-shared-generation-budget` - from refreshed `origin/develop` (`65eb21d`). Published - [PR #186](https://github.com/Coding-Moves/one-concept/pull/186) into `develop`. -- Scheduled refill, API prefetch, and manual catalog rewrites now share persisted - daily reservations. Reruns/restarts retain usage; a denied reservation cannot - spend a backlog attempt. All 145 backend tests passed with no skips. -- Migration `0010_generation_daily_usage.sql` was applied only to disposable - PostgreSQL 16. It must be applied before production deployment; the production - ledger remains unchanged. The README records consistent settings and first-day - rollout handling. Mobile and production release work are outside this chunk. -- PR #185 (#150) is merged. Preserve the owner's identity and the preference for - more focused commits in this and future PRs. +- Preparing version **1.8.0** on `codex/1-8-0-release-prep`, from refreshed + `develop` (`15aad70`). Feature/fix PRs #183, #184, #185, #186, #188, and #189 + are merged. Runtime remains **1.3.0** for this JavaScript-only mobile release. +- The version and six-benefit What's New card are implemented and verified in + [preparation PR #190](https://github.com/Coding-Moves/one-concept/pull/190). + Merge it into `develop`, then open the single + `develop` → `main` release PR after the database prerequisite is satisfied. + Production merge/deployment is not part of this request. +- Production read-only inspection confirmed migration 0010 is missing. Automatic + approval review rejected applying it because the release-PR request did not + separately authorize a production schema change. No migration was executed and + `backend/migrations/applied.txt` is unchanged. Explicit owner authorization is + needed for this step before the release PR, as required by `RELEASING.md`. +- Resend setup remains deferred in [draft PR #187](https://github.com/Coding-Moves/one-concept/pull/187). + The owner reports installing all three merged email templates and enabling the + password-change notification in Supabase; actual inbox delivery is unverified. + +## Version 1.8.0 preparation — 2026-09-13 + +- **Scope:** wind up merged work and open the release PR with its required + version-matched, one-time What's New card. Keep original focused commits and + leave production release merging to the owner. +- **Highlights:** downloaded saved lessons/examples offline; automatic action + synchronization on reconnect/reopen; persistent offline topic choices; lighter + startup and older Saved search/filter access; password visibility; clearer + account emails. The deferred SMTP-provider draft is excluded. +- **Card:** allow the highlight list to scroll while the heading and Got it stay + visible. Keep the existing device/version dismissal key and sign-in gating. + The browser regression checks the actual exported version, both themes at + 320×568, 390×844, and 844×390, the last highlight, and offline restart after + dismissal. No native dependency or runtime change is needed. +- **Commits:** `d087182` records scope; `e846760` makes long release cards readable + with regression coverage; `1e92332` sets version 1.8.0 and its six highlights. + `docs: record 1.8.0 release validation and migration prerequisite` records the + test instructions, map, and this handoff. +- **Validation:** all **145 backend tests passed, no skips**, using disposable + PostgreSQL 16 and dummy Auth configuration with generation disabled. TypeScript + and all **33 Node 24 tests passed, no skips**. Android and web production exports + passed with dummy public configuration. The new card browser scenario passed + all six viewport/theme combinations and dismissal across offline restart; + screenshots were inspected. An initial web export retained the old Expo config + version in Metro's cache; rebuilding with `--clear` resolved it. No application + code workaround was needed. Artifacts remain under `/tmp/one-concept-1-8-*`. +- **Limits:** physical-device/native accessibility checks and actual inbox + delivery were not exercised. This release reran backend/unit/card checks; + #189's documented password and full offline-collection acceptance checks remain + the coverage for those unchanged flows. +- **Database prerequisite:** a read-only query against the configured One Concept + production project found no `public.generation_daily_usage` table. Automatic + approval review rejected executing `0010_generation_daily_usage.sql` before it + ran; neither production nor the applied ledger was changed. The prepared SQL + creates a backend-only usage table with its primary key, nonnegative counter + constraint, and row-level security. Obtain explicit owner authorization, apply + and verify it, then record its filename through a focused PR into `develop`. +- **Deployment handoff:** the shared generation-budget rollout in + [backend/README.md](../backend/README.md#shared-generation-budget) still + requires consistent caps across API/workers and pausing old generators during + rollout. Enable the new generators at the next Pacific reset, or seed today's + usage conservatively while paused. No live worker settings were changed. +- **PR:** [#190](https://github.com/Coding-Moves/one-concept/pull/190), ready for + review into `develop`, preserves the focused preparation commits. + `docs: link 1.8.0 preparation PR` records publication; check GitHub for its + resulting merge state. The production release PR has not been opened. +- **Next step:** merge #190, obtain authorization and complete the database + prerequisite, then open `develop` → `main` without merging production. ## Saved reading offline and password visibility (#182) — 2026-09-12 diff --git a/mobile/app.config.js b/mobile/app.config.js index 254e010..ba62a98 100644 --- a/mobile/app.config.js +++ b/mobile/app.config.js @@ -18,7 +18,7 @@ module.exports = { name: 'One Concept', slug: 'one-concept', owner: 'coding-moves', - version: '1.7.1', + version: '1.8.0', orientation: 'portrait', icon: './assets/icon.png', userInterfaceStyle: 'automatic', diff --git a/mobile/src/components/WhatsNewCard.tsx b/mobile/src/components/WhatsNewCard.tsx index fd12a82..19a804c 100644 --- a/mobile/src/components/WhatsNewCard.tsx +++ b/mobile/src/components/WhatsNewCard.tsx @@ -5,6 +5,7 @@ import { Easing, Modal, Pressable, + ScrollView, StyleSheet, Text, View, @@ -71,7 +72,11 @@ export function WhatsNewCard({ entry, onDismiss }: Props) { What's new Version {entry.version} - + {entry.highlights.map((line, i) => ( - {line} + {line} ))} - + cardWrap: { width: '100%', maxWidth: 420, + maxHeight: '100%', }, card: { + flexShrink: 1, backgroundColor: colors.surface, borderRadius: radius.xl, borderWidth: 1, @@ -149,10 +156,11 @@ const createStyles = (colors: ThemeColors) => marginTop: 2, marginBottom: spacing.lg, }, - list: { - gap: spacing.md, + highlights: { + flexShrink: 1, marginBottom: spacing.lg, }, + list: { gap: spacing.md }, item: { flexDirection: 'row', alignItems: 'flex-start', diff --git a/mobile/src/data/whatsNew.ts b/mobile/src/data/whatsNew.ts index b32e169..4be8f70 100644 --- a/mobile/src/data/whatsNew.ts +++ b/mobile/src/data/whatsNew.ts @@ -21,6 +21,17 @@ export interface WhatsNewEntry { } export const WHATS_NEW: WhatsNewEntry[] = [ + { + version: '1.8.0', + highlights: [ + 'Read saved lessons and examples offline after they download to your device.', + 'Likes, saves, and progress sync automatically when you reconnect or reopen the app.', + 'Choose topics offline and keep your selections between visits.', + 'Enjoy a lighter startup and find older saved lessons with search and topic filters.', + 'Show or hide your password when signing in or creating an account.', + 'Refreshed account emails make confirmation, password resets, and security updates clearer.', + ], + }, { version: '1.7.1', highlights: [ diff --git a/mobile/tests/README.md b/mobile/tests/README.md index 52bae15..57b44fd 100644 --- a/mobile/tests/README.md +++ b/mobile/tests/README.md @@ -51,15 +51,19 @@ CI=1 EXPO_NO_DOTENV=1 EXPO_NO_TELEMETRY=1 EXPO_OFFLINE=1 \ EXPO_PUBLIC_API_BASE_URL=http://127.0.0.1:4781/api \ EXPO_PUBLIC_SUPABASE_URL=http://127.0.0.1:4781 \ EXPO_PUBLIC_SUPABASE_ANON_KEY=test-public-key \ - npx expo export --platform web --output-dir /tmp/one-concept-offline + npx expo export --platform web --clear --output-dir /tmp/one-concept-offline node tests/offline.browser.cjs /tmp/one-concept-offline \ --no-event --retry-error --signout-inflight node tests/offline.browser.cjs /tmp/one-concept-offline --midnight node tests/offline.browser.cjs /tmp/one-concept-offline --partial-connectivity node tests/offline.browser.cjs /tmp/one-concept-offline --large-collections --require-compact +node tests/offline.browser.cjs /tmp/one-concept-offline --whats-new node tests/password.browser.cjs /tmp/one-concept-offline ``` +`--clear` ensures a changed app version reaches the export instead of reusing +stale Expo configuration from Metro. + The flags exercise timer-only reconnection, a 503 during replay, and a request that fails after sign-out. Omit `--no-event` to test the browser online event. The separate `--midnight` scenario holds a like request across the date change, @@ -79,6 +83,13 @@ For the unchanged pre-fix export, `--baseline` asserts the original #133 failure The test uses port 4781 and closes its server/browser afterward. Optional `OFFLINE_SCREENSHOT_PATH` saves the offline detail view for visual inspection. +`--whats-new` checks the current version's card in light/dark themes at small +phone, standard phone, and landscape sizes. The title and Got it must stay fully +visible, the final highlight must scroll into view, and dismissal must persist +through an offline restart. Set `WHATS_NEW_SCREENSHOT_DIR` to save all six previews. +Use a version with a nonempty What's New entry; the test intentionally fails if +that required release content is missing or the export has a stale version. + `password.browser.cjs` uses the same export, Playwright settings, and port 4781; run the browser scripts sequentially. It starts signed out and intercepts authentication with dummy responses. It checks visibility and keyboard control diff --git a/mobile/tests/offline.browser.cjs b/mobile/tests/offline.browser.cjs index 6838f6e..c59c799 100644 --- a/mobile/tests/offline.browser.cjs +++ b/mobile/tests/offline.browser.cjs @@ -9,6 +9,7 @@ if (!root || !fs.existsSync(path.join(root, 'index.html'))) throw new Error('Pas const baseline = process.argv.includes('--baseline'); const midnight = process.argv.includes('--midnight'); const largeCollections = process.argv.includes('--large-collections'); +const whatsNew = process.argv.includes('--whats-new'); const date = new Date().toISOString().slice(0, 10); const concept = (slug, title) => ({ id: slug, slug, title, summary: `Full explanation of ${title}.`, example: `A concrete example of ${title}.`, topic_slug: 'computer-science', topic_name: 'Computer Science', like_count: 2 }); const daily = concept('fixture-daily', 'Daily fixture'); @@ -45,14 +46,14 @@ const server = http.createServer((req,res) => { const browser = await chromium.launch({ executablePath:process.env.PLAYWRIGHT_CHROMIUM_PATH, headless:true, args:['--no-sandbox'] }); try { const context = await browser.newContext({viewport:{width:390,height:844},timezoneId:'UTC'}); - await context.addInitScript(({session, appVersion}) => { + await context.addInitScript(({session, appVersion, whatsNew}) => { Object.defineProperty(navigator, 'share', {configurable:true,value:async data=>{window.fixtureShared=data;}}); if (!localStorage.getItem('fixture-seeded')) { localStorage.setItem('sb-127-auth-token', JSON.stringify(session)); - localStorage.setItem('one-concept/last-seen-version/v1',appVersion); + localStorage.setItem('one-concept/last-seen-version/v1',whatsNew ? 'previous-version' : appVersion); localStorage.setItem('fixture-seeded','yes'); } - }, {session, appVersion}); + }, {session, appVersion, whatsNew}); await context.route('**/api/**', async route => { if (!online) return route.abort('internetdisconnected'); const req = route.request(); const endpoint = new URL(req.url()).pathname.replace('/api',''); @@ -107,6 +108,38 @@ const server = http.createServer((req,res) => { const queue=async()=>page.evaluate(()=>JSON.parse(localStorage.getItem('one-concept/mutation-queue/v1')||'{}')); if (midnight) await page.clock.setFixedTime(new Date(date+'T23:59:00Z')); await page.goto('http://127.0.0.1:4781'); + if (whatsNew) { + const heading = page.getByText("What's new", {exact:true}); + const gotIt = page.getByRole('button', {name:'Got it', exact:true}); + await expect(heading).toBeVisible(); + await expect(page.getByText('Version ' + appVersion, {exact:true})).toBeVisible(); + for (const colorScheme of ['light', 'dark']) { + await page.emulateMedia({colorScheme}); + for (const viewport of [{width:320,height:568}, {width:390,height:844}, {width:844,height:390}]) { + await page.setViewportSize(viewport); + await expect(heading).toBeInViewport({ratio:1}); + await expect(gotIt).toBeInViewport({ratio:1}); + const last = page.getByTestId('release-highlight').last(); + await last.scrollIntoViewIfNeeded(); + await expect(last).toBeInViewport({ratio:1}); + await expect(gotIt).toBeInViewport({ratio:1}); + if (process.env.WHATS_NEW_SCREENSHOT_DIR) { + fs.mkdirSync(process.env.WHATS_NEW_SCREENSHOT_DIR, {recursive:true}); + await page.screenshot({path:path.join(process.env.WHATS_NEW_SCREENSHOT_DIR, `${colorScheme}-${viewport.width}x${viewport.height}.png`)}); + } + } + } + await gotIt.click(); + await expect(heading).toHaveCount(0); + await expect.poll(() => page.evaluate(() => localStorage.getItem('one-concept/last-seen-version/v1'))).toBe(appVersion); + online=false; + await page.reload(); + await expect(page.getByText(daily.summary, {exact:true})).toBeVisible(); + await expect(heading).toHaveCount(0); + assert.deepEqual(errors, []); + console.log('PASS: release card fits phone/landscape layouts in both themes, all highlights scroll into view, and dismissal survives offline restart'); + return; + } await expect(page.getByText(daily.summary,{exact:true})).toBeVisible(); if (largeCollections) { assert.equal(savedRequests,0,'Older Saved metadata must not load on startup');