crowd-source-faq

June 4 Session Context — UI Glassmorphism + Access Control

Two UI passes (light-mode parity + ghost-button redesign) and one access-control pass. Backend untouched on UI; access-control pass touched 4 backend files + 2 frontend files. All changes typecheck clean on both sides.


1. Light-mode glassmorphism search overlay (text2.txt spec)

Spec: text2.txt

Refactor ONLY search-overlay colors, blur, and contrast in light mode — DO NOT change layout. Fix:

Files changed

CSS classes added (lines 530–700 of index.css)

Class Light Dark
.search-overlay rgba(0,0,0,0.25) + blur(16px) rgba(0,0,0,0.5) + blur(20px) + green glow
.search-panel rgba(255,255,255,0.7) + blur(20px) + linear-gradient(135deg, rgba(34,197,94,0.05), rgba(255,255,255,0.6)) + rgba(0,0,0,0.08) border + 0 20px 50px rgba(0,0,0,0.15) shadow dark charcoal glass
.search-bar-input #FFFFFF bg, #111827 text, focus rgba(34,197,94,0.4) border + 0 0 0 3px rgba(34,197,94,0.15) glow semi-transparent dark
.search-list-item #FFFFFF bg, #111827 text; hover rgba(0,0,0,0.03); active rgba(34,197,94,0.08) + rgba(34,197,94,0.2) border dark glass
.search-pill rgba(0,0,0,0.04) bg, #374151 text; hover rgba(34,197,94,0.08) + #16A34A dark glass
.search-skeleton (new) rgba(255,255,255,0.5) bg + rgba(0,0,0,0.06) border rgba(255,255,255,0.04) + rgba(255,255,255,0.06)

Architecture decision

Use [data-theme="dark"] CSS overrides, not dark: Tailwind classes in JSX. The CSS class becomes self-contained — both modes defined in one place — preventing the dark: class in JSX from accidentally leaking into light mode. Component-level dark: overrides were stripped where the CSS class already handles both modes.

What I would NOT touch


Spec: text3.txt

Refactor ONLY these two elements inside the search overlay in BOTH light and dark modes. DO NOT change layout.

Files changed

CSS classes added (lines 680–810 of index.css)

.search-ask-btn — Ghost button:

.search-popular-pill — Enhanced pill with count badge:

.search-popular-badge — Count badge (ml-auto):

Bug fixed (incidental)

The pre-existing CTA.tsx “Ask the Community” button used btn-cta which styles as dark bg + white text (bg-ink text-accent-text). In light mode this is hardcoded to look like an inverted button — clearly wrong. The new .search-ask-btn is theme-correct.


No UI/styling/layout changes. Minimum backend/frontend permission surface.

Files changed

Public endpoints (no auth required)

FAQ (backend/routes/faq.ts):

GET  /api/faq
GET  /api/faq/paginated
GET  /api/faq/recent
GET  /api/faq/:id
GET  /api/faq/:id/history

Community (backend/routes/community.ts):

GET  /api/community
GET  /api/community/search
GET  /api/community/solved
GET  /api/community/answers/list
GET  /api/community/stats
GET  /api/community/:id
GET  /api/community/:id/related

AI Search (backend/routes/askAi.ts):

POST /api/ask-ai   — public, anon throttled to 20/min per IP, authed 30/min per IP

Protected endpoints (unchanged)

All POST / PATCH / PUT / DELETE on /api/faq/* and /api/community/*, plus:

GET  /api/community/bookmarks        (user-specific)
GET  /api/community/review-queue     (admin/moderator)
GET  /api/notifications
GET  /api/admin/*
GET  /api/moderation/*

Anonymous AI search quota (frontend enforcement)

AskAIButton.tsx tracks:

Behavior:

AI search backend rate limit (backend/routes/askAi.ts)

const anonAiLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 20,        // defense-in-depth — frontend enforces 5/day
  keyGenerator: (req) => `anon:${ipKeyGenerator(req.ip ?? 'unknown')}`,
  skip: (req) => !!(req.headers.authorization?.startsWith('Bearer ')),
});
const authedAiLimiter = rateLimit({ windowMs: 60 * 1000, max: 30, ... });

If a Bearer token is present, the request goes through protect (best-effort verification) and then the authedAiLimiter; otherwise it skips straight to the controller under the anonAiLimiter. Invalid/expired tokens fall through to the anon path — public access is the default, auth only changes the rate limit.

401 interceptor fix (frontend/src/utils/api.ts)

The old interceptor opened the sign-in modal on ANY 401 response — including cases where an anonymous user hit a (formerly) protected public endpoint. Now it only opens the modal if localStorage.getItem('yaksha_token') was truthy BEFORE the 401 fired (i.e., the user had a real session that just expired). Anonymous 401s silently clear the (non-existent) token.

Pre-existing 500 bug (NOT fixed — out of scope)

GET /api/community/bookmarks and /api/community/review-queue return 500 instead of 401 for anonymous users. The authorize() middleware in authShared.ts calls next(new Error('Insufficient permissions.')) without setting err.status, and the global error handler falls through to 500. Pre-existing bug on admin-only routes that anonymous users wouldn’t hit in normal product flow. Per the “do not refactor unrelated code” rule, left as-is. Trivial fix if/when needed: add err.status = 403 in the authorize factory.

Verification (curl, no token)

200  GET  /api/faq
200  GET  /api/faq/paginated
200  GET  /api/faq/recent
200  GET  /api/community
200  GET  /api/community/solved
200  GET  /api/community/stats
200  GET  /api/community/answers/list
200  GET  /api/search/trending
200  GET  /api/reputation/leaderboard
200  POST /api/ask-ai
401  POST /api/community
401  POST /api/faq/check-match
401  PATCH /api/faq/x/feedback
401  POST /api/faq/x/report
401  POST /api/community/x/bookmark
401  GET  /api/notifications

All public endpoints return 200 without auth. All write/admin endpoints still return 401.


4. Files NOT modified in this session (despite being in git status)

The working tree has 57 modified + 4 untracked files vs. the last commit 47add56. Many are pre-existing uncommitted changes from earlier sessions today (security audit, dark-mode token pass, admin duplicate-header sweep, etc.). The session touched:

UI session (sections 1 + 2):

Access control session (section 3):

Total: 6 frontend files + 4 backend files = 10 files in this session. The other 51 uncommitted files are from prior work today (security audit, dark mode, etc.) — not in scope for this context doc.


5. Quick facts / state to remember


6. Open follow-ups (for next session)