Skip to content

feat(save): save-first E-01 to E-05 let logged-out learners save and sign in later - #65

Merged
navidshad merged 2 commits into
mainfrom
save-first/extension-main
Oct 16, 2026
Merged

navidshad merged 2 commits into
mainfrom
save-first/extension-main

Conversation

@navidshad

@navidshad navidshad commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Logged-out learners can now save a phrase. It is kept in the extension and sent to their account after they sign in. No server change.

Why: subturtle-doc docs/tech/save-first-implementation.md (rows save-first E-01 to E-05). In the last 90 days, 246 people looked up a word while logged out and only 16 while logged in, and logged-out people could not save.

save-first E-01: "Learn more or Save"

  • The selection popup button says Learn more or Save (was "Save and Learn"). It still says Learn when the phrase is saved.
  • "Learn" now also shows for a phrase saved on this browser, and its stored translation is reused (no new translation request).
  • The popup help text matches.

save-first E-02: Save without login

  • On the word card, a logged-out learner sees two buttons from the first phrase on (new LocalSaveSection.vue): Save without login and Login to save. (Design change from Navid; the first version showed only Save.)
  • Login to save opens the login window (source save_prompt) and keeps the save-login-prompt_viewed (once per word) and save-login-prompt_clicked events. Save without login sends save-without-login_clicked (count).
  • Save puts the phrase in chrome.storage.local (pendingSaves, one list). It keeps everything createPhrase needs (phrase, translation, language, context, direction, language info, linguistic data, chunks, source URL), plus the page title and the time.
  • Saving the same phrase twice keeps one copy. The card shows "Saved. Kept on this browser until you sign in."
  • phrase_saved gets logged_in: false. Logged-in saves now send logged_in: true, so the two can be compared.
  • There is no bundle choice for a logged-out save (see E-04).

save-first E-03: ask for sign-in after a few saves

  • From the 3rd local phrase, each save shows a card: "Sign in to keep your N phrases and review them tomorrow", with Sign in and Not now. Sign in uses the same login window (source save_prompt).
  • At 50 local phrases, the card replaces Save (no "Not now").
  • Events: save-signin-card_viewed (count, required), save-signin-card_clicked (count), save-signin-card_dismissed (count).

save-first E-04: send local saves after sign-in

  • New pending-saves-sync.ts. It runs:
    • right after a login in the login window (which shows "Your N saved phrases are in your account"),
    • when the toolbar popup opens signed in,
    • when a page loads with a signed-in user, so a run that was cut off carries on.
  • Each phrase is sent with createPhrase and deviceId. It leaves the list only after the server accepts it.
  • One run at a time: a lock in chrome.storage.local (ends by itself after 2 minutes). If two tabs still race, the server already reuses a phrase with the same text, so no duplicates.
  • At the Starter save limit it stops, keeps the rest, and says so (login window and popup).
  • It does not use withAuthRetry, because that falls back to the shared anonymous account.
  • Event: pending-saves_synced (sent, failed, left, and limit_reached when true).

save-first E-05: waiting phrases in the popup

  • A logged-out learner with local phrases sees "N phrases waiting. Sign in to keep them" with a Sign in button at the top of the toolbar popup.

Decisions different from the work list

  • Bundle name. The work list says to store the suggested bundle name at save time. That is not possible: the suggestion (getBundleSuggestionForPage) needs a signed-in user. So the page URL and title are stored, and the sync asks for the suggestion after sign-in. This also reuses a bundle the user already has for that page. If there is no suggestion, the phrase goes to a bundle called "Saved phrases". Phrases saved from the toolbar popup have no page, so they go there too.

How tested

  • New unit tests: pending-saves, pending-saves-sync, local-save (word card), pending-saves-banner, selection-popup-label, plus login-window sync messages in login-analytics.
  • Writing this showed a real bug, now fixed: Vue props are reactive proxies, and storage cannot keep them as they are. The list stores a plain JSON copy.
  • yarn typecheck: clean.
  • yarn test: 40 files, 334 tests pass.
  • yarn build: OK.
  • yarn test:e2e (CI stub settings): 22 of 22 pass.
  • The repo has no lint script.
  • Screenshots (light and dark) of the new UI were taken with the repo's Playwright extension fixture against this build; they are shared outside the repo.

🤖 Generated with Claude Code

…sign in later

- save-first E-01: the selection popup button says "Learn more or Save"
  ("Learn" when saved, now also for a phrase saved on this browser, whose
  translation is reused). The popup help text matches.
- save-first E-02: a logged-out learner gets Save on the word card instead
  of "Login to save this phrase" (new LocalSaveSection). The phrase goes
  to chrome.storage.local (pendingSaves, one list) with everything
  createPhrase needs plus the page URL and title. One copy per phrase.
  phrase_saved gets logged_in: false (logged-in saves get true).
- save-first E-03: from the 3rd local phrase, each save shows "Sign in to
  keep your N phrases and review them tomorrow" with Sign in (login source
  save_prompt) and Not now. At 50 local phrases the card replaces Save.
  Events save-signin-card_viewed (count, required), _clicked, _dismissed.
- save-first E-04: after sign-in (login window, popup open, or a page
  load with a signed-in user) each local phrase goes to the account with
  createPhrase and deviceId, and leaves the list only when the server
  accepts it. The bundle comes from getBundleSuggestionForPage for the
  saved page, else "Saved phrases". One run at a time (a lock in
  chrome.storage.local), stops at the save limit and keeps the rest.
  Event pending-saves_synced (sent, failed, left).
- save-first E-05: the toolbar popup shows "N phrases waiting. Sign in to
  keep them" to logged-out learners with local phrases.
- The old save-login-prompt_viewed / _clicked events go with the old
  button; the sign-in card events replace them.

Work list: subturtle-doc docs/tech/save-first-implementation.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ave" together

Design change from Navid: from the first phrase on, a logged-out learner
sees both choices on the word card.

- "Save without login" saves on this browser (as before) and sends
  save-without-login_clicked (count).
- "Login to save" opens the login window (source save_prompt). The
  existing save-login-prompt_viewed (once per word, when the choices show)
  and save-login-prompt_clicked events stay on it.
- The soft sign-in card from the 3rd local phrase and the 50-phrase cap
  (only sign-in offered) are unchanged.

Work list: subturtle-doc docs/tech/save-first-implementation.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@navidshad
navidshad merged commit aca8a98 into main Oct 16, 2026
2 checks passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.19.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant