Commit graph

4 commits

Author SHA1 Message Date
ScreenTinker 674a34ba45 feat(config): HIDE_BILLING flag to hide the Subscription/billing UI (#116)
Opt-in, default-off UI gate (per strobe's spec; verified his file refs first).
When set, hides the Subscription sidebar item + billing view and bounces
#/billing to the dashboard. Billing shown by default -> existing deployments
unchanged. UI-only: /api/subscription/* untouched (internal usage reads stay).

- config.js: config.hideBilling from HIDE_BILLING (mirrors selfHosted).
- auth.js: surface hide_billing on GET /api/auth/me (client already fetches it
  at boot, stored on the user object).
- index.html: id="billingNavItem" on the Subscription <li> (mirrors adminNavItem).
- app.js: toggle billingNavItem in updateSidebarUser (next to the admin toggle);
  guard #/billing -> history.replaceState('#/') + render dashboard (replaceState
  so the back button doesn't loop into the guard).
- .env.example + README documented.

Spec assumptions verified against code: adminNavItem toggle pattern exists;
/me is fetched at boot and updateSidebarUser runs both at boot (cached user)
and post-/me, so no-flash holds on warm loads (one-time flash possible on the
first load after the flag flips — same as the admin nav, minor); route dispatch
is an if/else chain. Nav label is static (no data-i18n) so no i18n change.

Validated (headless Chrome, both states):
- flag unset -> Subscription tab present, #/billing renders (backward-compat).
- HIDE_BILLING=true -> tab hidden, #/billing redirects to #/.
- config maps HIDE_BILLING both ways; live /me default hide_billing=false.
- 149 server tests green. Default-off = zero change for existing deployments.

Known cosmetic (harmless): after the redirect the billing nav LINK keeps its
'active' class, but the nav item is display:none so it's never visible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 09:19:24 -05:00
ScreenTinker 406f481a57 docs: surface DISABLE_REGISTRATION self-hosting flag (#11)
DISABLE_REGISTRATION already closes public self-service signup (first-user
setup on an empty DB still allowed) and the login page already hides its
"Create account" button when it's set - but the flag was easy to miss: it was
in the README env-var table yet absent from .env.example (the file
self-hosters actually copy) and from the README systemd unit example.

- .env.example: document DISABLE_REGISTRATION + DISABLE_HOMEPAGE under the
  Self-hosting section.
- README: add commented Environment= lines for both to the systemd example,
  noting the login UI hides the signup button to match.

Docs only - no code change. Backend gate (routes/auth.js canRegister +
/auth/config registration_enabled) and the login.js hiding already behave
correctly; verified registration_enabled flips to false under the flag.

Closes #11.
2026-06-08 10:29:08 -05:00
ScreenTinker cbe00d6c85 feat(signup): T+3 activation nudge for users with zero paired screens
Daily sweep (15:00 UTC) emails a warm, personal "checking in" message
to users who signed up 3-14 days ago and still have no paired screen,
nudging them toward activation. Once per user, reuses the Graph
transport (services/email.js) via the existing fromName/rawSubject
options.

- New service services/activationNudge.js, started from server.js.
  Self-correcting daily scheduler (recompute next 15:00 UTC each run;
  no node-cron dependency).
- Eligibility (Option B, workspace-aware): created 3-14 days ago,
  activation_nudge_sent_at IS NULL, COALESCE(email_alerts,1)=1 (only
  an explicit opt-out of 0 is excluded; NULL/unset still qualify), and
  ZERO devices owned by the user OR present in any workspace they
  belong to. The workspace check avoids nudging engaged team members.
- Idempotency: activation_nudge_sent_at, stamped after send; paired
  sentinel-1 backfill so the first sweep can't blast the dormant
  legacy base. Only genuinely-new signups become eligible.
- GATE: HOSTED_INSTANCE=true (positive hosted signal, NOT !selfHosted).
  A daily bulk sweep would be far worse to leak than a single email, so
  a self-hoster who configured Graph but missed SELF_HOSTED won't blast
  their user base. Unset -> neither scheduled nor sent. Documented in
  .env.example.
2026-05-30 20:28:24 -05:00
ScreenTinker 2f78fa1106 chore: track .env.example (un-ignore from .env.* rule)
The prior commit's .env.example was silently dropped by the .env.*
gitignore rule. Add a "!.env.example" negation so the documented
template (placeholders only, no secrets) is tracked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 16:16:52 -05:00