peregrine/web/src/composables/useFeatureFlag.ts
pyr0ball 49e3265132 feat(web): merge Vue SPA from feature/vue-spa; add ClassicUIButton + useFeatureFlag
- Import web/ directory (Vue 3 + Vite + UnoCSS SPA) from feature/vue-spa branch
- Add web/src/components/ClassicUIButton.vue: switch-back to Streamlit via
  cookie (prgn_ui=streamlit) + ?prgn_switch=streamlit query param bridge
- Add web/src/composables/useFeatureFlag.ts: reads prgn_demo_tier cookie for
  demo toolbar visual consistency (not an authoritative gate, see issue #8)
- Update .gitignore: add .superpowers/, pytest-output.txt, docs/superpowers/
2026-03-22 18:46:11 -07:00

48 lines
1.6 KiB
TypeScript

/**
* useFeatureFlag — demo toolbar tier display helper.
*
* Reads the `prgn_demo_tier` cookie set by the Streamlit demo toolbar so the
* Vue SPA can visually reflect the simulated tier (e.g. in ClassicUIButton
* or feature-locked UI hints).
*
* ⚠️ NOT an authoritative feature gate. This is demo-only visual consistency.
* Production feature gating will use a future /api/features endpoint (issue #8).
* All real access control lives in the Python tier system (app/wizard/tiers.py).
*/
import { computed } from 'vue'
const VALID_TIERS = ['free', 'paid', 'premium'] as const
type Tier = (typeof VALID_TIERS)[number]
function _readDemoTierCookie(): Tier | null {
const match = document.cookie
.split('; ')
.find((row) => row.startsWith('prgn_demo_tier='))
if (!match) return null
const value = match.split('=')[1] as Tier
return VALID_TIERS.includes(value) ? value : null
}
/**
* Returns the simulated demo tier from the `prgn_demo_tier` cookie,
* or `null` when not in demo mode (cookie absent).
*
* Use for visual indicators only — never for access control.
*/
export function useFeatureFlag() {
const demoTier = computed<Tier | null>(() => _readDemoTierCookie())
const isDemoMode = computed(() => demoTier.value !== null)
/**
* Returns true if the simulated demo tier meets `required`.
* Always returns false outside demo mode.
*/
function demoCanUse(required: Tier): boolean {
const order: Tier[] = ['free', 'paid', 'premium']
if (!demoTier.value) return false
return order.indexOf(demoTier.value) >= order.indexOf(required)
}
return { demoTier, isDemoMode, demoCanUse }
}