Postmortem: We Put the Gate in the Wrong Place
TLDR
The analyze gate blocked users at the run limit before they got value. The export gate blocks at download, after they have gotten value. Moving the gate required deleting ~80 lines of run-counter logic and extracting a shared auth modal from the shell.
What We Built
A guest run counter in the analyze panel. GUEST_KEY tracked how many times a user had run analysis in localStorage. GUEST_LIMIT was 2. On the third run, _requestAnalysisPermission() fired. If the shell returned "not signed in," _renderAnalyzePaywall() overlaid the canvas with a blurred gradient and a sign-up CTA.
The logic was thorough. The placement was wrong.
What Failed
The gate fired at usage, not at intent to save. A user who ran analysis twice and extracted exactly the data they needed left without ever encountering sign-in. The gate caught people who were curious and actively exploring, not people who had found value and wanted to keep it.
The specific failure: exporting a PDF extraction (the moment with the highest intent signal) had no gate at all on standalone tool pages. The download button just downloaded. The run gate fired on analysis, which is free-exploration behavior. The download button, which is clearly "I want to keep this," was completely open.
What We Threw Away
GUEST_KEY, GUEST_LIMIT, _getGuestCount, _incrementGuestCount, _checkGuestLimit, _requestAnalysisPermission, _updateStatusMessage, _renderAnalyzePaywall, and the paywall CSS block. Approximately 80 lines of code removed from analyzePanel.js.
From os-shell.js: _checkAnalysisTier, ANALYZE_GUEST_KEY, ANALYZE_GUEST_LIMIT, ANALYZE_FREE_LIMIT, _getAnalyzeGuestCount, _incrementAnalyzeGuest, the gx:request-analysis postMessage handler, _buildAuthModal (~140 lines of DOM construction), _authModalEl, _setAuthMsg, and 13 debug console.log lines.
What Survived
The analysis pipeline itself. The canvas, the region overlays, the pipeline sliders, the re-extraction flow. All of that runs freely now. The gate is gone from there entirely.
The shell's auth state management survived. _authUser, _getStoredToken, _initAuth. These are correct and load-bearing. Only the modal DOM construction was replaced.
What Replaced It
GxGate: a small script loaded by each tool that hooks export buttons and calls checkAuth() before allowing the download. Three resolution paths: VS Code extension (always signed in), OS shell (postMessage), standalone (GxAuth modal).
GxAuth: a shared auth modal extracted from the shell's _buildAuthModal. Loads as a plain script, injects once into document.body, handles Google and GitHub OAuth, dispatches gx:signed-in and gx:auth-modal-closed.
Both are in assets/js/ and synced to all three extension packages.
The Lesson
Gate at the moment of value extraction, not at the moment of exploration. A user running analysis is exploring. A user clicking Export has already decided the tool is useful. Those are different behavioral signals and they warrant different gates.