Free premium slots: 2 premium features for signed-in users#213
Merged
lawrencehook merged 15 commits intomainfrom Apr 20, 2026
Merged
Free premium slots: 2 premium features for signed-in users#213lawrencehook merged 15 commits intomainfrom
lawrencehook merged 15 commits intomainfrom
Conversation
Signed-in non-premium users can now activate up to 2 premium features (FREE_PREMIUM_SLOTS). Enforcement lives in updateSetting / logStorageChange so direct toggles and effect-chained writes are clamped consistently. Toggle clicks at the slot limit reuse the upgrade modal with slot-limit copy. New html[tier] attribute drives styling; header shows "N/2 free premium" when applicable. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
chrome.management.getSelf() / browser.management.getSelf() reports installType 'development' for unpacked/temporary installs (Chrome --load-extension, Firefox web-ext run). Guard the first-install welcome tab on that so local reloads and the UI test harness do not pop a new tab every time the profile is fresh. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
YouTube migrated the subscriptions feed to yt-lockup-view-model with yt-thumbnail-view-model inside, so `ytd-thumbnail` no longer matches the feed thumbnails there. Add the new element to the remove_video_thumbnails hide rule and the blur_video_thumbnails filter rule so both features work on /feed/subscriptions again. Caught by the rys-test Playwright harness. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- New "free" sidebar (left of all/active) filters to non-premium options and hides sections that have none. Default for non-premium tiers. - Sticky label in the top-right of #primary_options shows the current sidebar selection or search query. Uses margin-bottom: -28px so it overlays without pushing content down; left+bottom border gives it a tab-cutout look flush with the top/right edges. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- Fix: content-script referenced PREMIUM_CONFIG.FREE_PREMIUM_SLOTS but config.js was not loaded by either manifest's content_scripts.js — would have thrown for free_signed_in users on YouTube pages. Added /shared/config.js to the chrome + firefox content-script manifests. - Extract clearAllPremium / enforceSlotBudget helpers to shared/main.js; both content-script and options/main.js now use them at load. - Simplify the token-change handler in content-script's logStorageChange. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
"Sign in to pick any 2 premium features. Upgrade to unlock them all." Frames the signed-in free tier around user choice rather than a "premium required" gate. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
… tier - Schedule and Password are now in PREMIUM_FEATURE_IDS, so signed-in free users can activate them as one of their 2 freebies. New canUsePremiumFeature(settingId) helper in settings-menu.js gates the menu clicks: at 2/2 slots (and not already active) it opens the upgrade modal with slot-limit copy, otherwise it opens the feature modal. - Remove html[is_premium] attribute. Tier covers the same info (premium | free_signed_in | free). Updated ~6 CSS selectors and ~4 JS checks from is_premium to tier='premium'. No behavior change. - Scope the blue premium-marker dot to non-premium tiers (was visible to all users, now only shown when the dot carries signal). - Typography: split the premium-required modal and slot-limit note into main/sub spans for softer hierarchy. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ontrast - Replace the padlock on Schedule/Password/Edit Schedule menu items with the same blue dot used on main-list premium options. Opacity bumped from 0.25 to 0.65 to match the main list and reflect that these are now activatable as freebies by signed-in free users. - Password modal buttons (Confirm/Remove Password/Unlock) were using color: var(--body-color) with a browser-default light background, so in dark mode the text became near-invisible (light on light). Apply the same blue bg + white text + disabled fade used by the other modal buttons. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- Set html[tier] synchronously in options/main.js right after
License.getTierSync(), before populateOptions() renders or wires click
handlers. Avoids a window where premium clicks saw tier===null during
initAccountState()'s async init and misrouted to the signed-out modal.
- New pruneToSlotBudget() in settings-menu.js runs when updatePremiumUI
transitions into free_signed_in (subscription lapsed while options is
open). It delegates the "keep first N" rule to shared enforceSlotBudget
and routes over-budget IDs through updateSetting so DOM, cache, slot
indicator, storage, and open content-script tabs stay in sync.
- disableAllPremiumFeatures() replaces the sign-out handler's SECTIONS
loop (which missed schedule/password) and is also called from
updatePremiumUI({ signedOut: true }) so a 401 prunes state the same
way an explicit sign-out does.
- Switch background install handler to await browser.management.getSelf().
Chrome MV3 returns a Promise when no callback is passed; Firefox MV2
is promise-native and silently ignores callbacks. Previous callback
form dropped the welcome-tab open on real Firefox installs.
management.getSelf is exempt from the management permission, so no
manifest change needed.
- Remove unused handlePremiumFeatureClick() (dead code since the switch
to canUsePremiumFeature()).
- Document the clearAllPremium() / enforceSlotBudget() API asymmetry in
shared/main.js: clearAllPremium is in-memory only so premium state
returns on re-upgrade; enforceSlotBudget returns a writeBack map so
over-budget settings don't re-surface on next load.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Replace raw 'premium' / 'free_signed_in' / 'free' string literals in JS with TIER.PREMIUM / TIER.FREE_SIGNED_IN / TIER.FREE. The new constant lives in shared/config.js as a frozen object — loaded by both options and content-script via the existing script order. CSS selectors (html[tier='premium']) keep string literals since they target the underlying HTML attribute, which still holds the string value. UI labels like the "free" sidebar tab text and data-status attributes are separate concerns and untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
lawrencehook
added a commit
that referenced
this pull request
Apr 20, 2026
Lets signed-in free-tier users enable up to 2 premium features at no cost.
lawrencehook
added a commit
that referenced
this pull request
Apr 20, 2026
Lets signed-in free-tier users enable up to 2 premium features at no cost.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #212
Summary
Lets signed-in non-premium users activate up to 2 premium features (
FREE_PREMIUM_SLOTS = 2insrc/shared/config.js) as freebies. Any excess is clamped at the storage-write layer, so both direct toggles and cascadedeffectswrites behave the same.Tier model
New frozen
TIERconstant inshared/config.js:TIER.PREMIUM— valid paid licenseTIER.FREE_SIGNED_IN— signed in, no paid license (gets the 2 slots)TIER.FREE— signed outLicense.getTierSync(licenseToken, sessionToken)resolves the tier. Anhtml[tier]attribute drives CSS + JS gates and is set synchronously from local storage before the options page renders, then refined after the async license check.Enforcement
Single clamp point per runtime:
src/options/main.js#updateSetting— on any premium-setting writesrc/content-script/main.js#logStorageChange— on any premium-setting storage changeBoth reuse shared helpers in
src/shared/main.js:PREMIUM_FEATURE_IDS/PREMIUM_FEATURE_ID_SET(includes schedule & password)countActivePremium(cache)clearAllPremium(settings)— in-memory only (storage preserved so premium state returns on re-upgrade)enforceSlotBudget(settings, slotLimit)— mutates + returns writeBack mapUI
N/2 free premium features used. Visible only onTIER.FREE_SIGNED_IN.html:not([tier='premium']). Same treatment applied to menu-level premium options (Schedule, Password, Edit Schedule). Premium users see no marker.{ reason: 'slot_limit' }option: when the slot budget is full, shows a short note ("Free tier allows 2 premium features. Upgrade or deselect one.") above the checkout plans. Modal width capped to prevent the note from stretching the layout.#primary_options: sticky on scroll, shows the active sidebar label or the search query in quotes.premium-note-main/premium-note-subspans for softer hierarchy.Tier transitions
TIER.FREE_SIGNED_INimmediately, refined byrefreshLicense().disableAllPremiumFeatures()loopsPREMIUM_FEATURE_IDS(including schedule/password) viaupdateSetting, which cascades through storage to open content-script tabs.pruneToSlotBudget()keeps the first 2 active premium features and disables the rest. Delegates the "keep first N" rule to sharedenforceSlotBudget().Menu-level features
Schedule and Password live in
OTHER_SETTINGS(notSECTIONS) but are premium-gated. They're added toPREMIUM_FEATURE_IDSso they count toward the slot budget. A newcanUsePremiumFeature(settingId)helper gates their menu-click handlers: at 2/2 slots (and not already active) it opens the upgrade modal with slot-limit copy; already-active features can always be opened for management.Grandfathered / expired premium
Grandfathered users are unaffected — they show as premium via the existing license flow. Expired-premium users fall back to
TIER.FREE_SIGNED_IN, and the initial load callsenforceSlotBudgetso their first 2 premium settings stay on and the rest are cleared (both in-memory and persisted).Firefox install handler fix
src/background/events.jsnow awaitsbrowser.management.getSelf()instead of passing a callback. Chrome MV3 returns a promise; Firefox MV2 silently ignored the callback — the welcome tab previously never opened on real Firefox installs after the dev-install skip was added onmain.Manifests
shared/config.jsis now loaded into the content-script context in bothchrome_manifest.jsonandfirefox_manifest.json(required forPREMIUM_CONFIG.FREE_PREMIUM_SLOTSandTIER).🤖 Generated with Claude Code