Toolbox is still being refined; hide it from clients on prod while keeping it
usable on staging. Entry points (header link, dashboard card) render only when
TOOLBOX_VISIBLE=true; a tools/layout redirects /tools/* to /dashboard otherwise.
Routes and ToolUsage table are untouched (no destructive migration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rendering <head dangerouslySetInnerHTML={headCode}> in the App Router root
layout let the manual <head> overwrite the React-hoisted <link rel=stylesheet>
during hydration — all Tailwind styles dropped for real users once JS ran
(headless snapshots taken before hydration looked fine). headCode held the
Yandex.Metrika snippet. Replace with HtmlInjector client component that appends
the code through the DOM post-hydration, recreating <script> nodes so they run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Curator list labeled APPROVED-without-feedback submissions as 'Без ответа'
(badge keyed on feedbacks only). Key it on status: Принято / С отзывом /
На рассмотрении / Отклонено / Без ответа. Dashboards already count by status.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two more inside-LMS generators under /tools, completing the 6-tool set:
- Dataview: DQL builder (TABLE/LIST/TASK/CALENDAR, FROM/WHERE/SORT/LIMIT),
output wrapped in a dataview code block
- Bases: .base YAML builder (views, filters and:, order, sort, groupBy, limit);
filter expressions emitted as single-quoted YAML scalars for safety
Syntax verified against official Obsidian docs (research workflow). Audit fix:
Bases sort entries use 'column:' (not 'property:') per documented working .base
files; 'property:' is kept only for groupBy. 14 new tests (47 total).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inside-LMS toolbox for registered students: callout-CSS, YAML-frontmatter,
theme CSS-variables, Style Settings generators. Auth inherited from (student)
route group; no public surface, no email gate.
- Pure generators in src/lib/tools/* with Vitest (33 tests)
- Shared YAML-safe scalar quoter (_shared/yaml.ts) used by frontmatter +
style-settings: unquoted user input was silently breaking YAML (tags '#x'
-> null, color default '#7C3AED' -> null, 'a: b' -> parse error)
- hex validation in callout (no NaN), date input constrained, clipboard +
analytics calls guarded
- Prisma ToolUsage model + migration; logToolUsage Server Action (auth-first,
10-min dedup window per user/tool)
- Tool index /tools + ToolCard (explicit lucide map, no import *) + header link
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- window.ym reachGoal 'signup_free' on registration success
- GateViewTracker client component fires 'gate_view' on tripwire gate mount
- counter id via NEXT_PUBLIC_METRIKA_COUNTER_ID (build-time)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /api/internal/quiz-lead accepts archetype quiz results and
idempotently upserts subscriber in Listmonk (create on 201, PUT on 409).
Documents QUIZ_LEAD_SECRET and LISTMONK_* vars in .env.example.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Attachments uploaded via question-upload get a CDN URL
(files.second-brain.ru via S3_CDN_URL), but the validator only accepted
the direct S3 endpoint prefix — so every attachment was silently dropped
(message text saved, file lost). Add isAllowedPublicUrl (CDN + direct S3)
in lib/s3 and use it in both question routes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Curators/admins can mark a submission APPROVED (and complete the lesson)
without writing feedback. The student sees 'ДЗ принято ✓' and can no
longer resubmit. No email is sent on silent approval.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mustChangePassword guard redirected an impersonating admin to
/change-password, which sits outside the student layout (no
return-to-admin banner) — a dead end. Skip the guard when impersonating,
and add the stop-impersonate banner to the change-password page as a
fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lessons with homework/quiz have no manual complete button — they
auto-complete on submission. Before submission nothing was shown,
leaving students unsure how to progress. Add a muted hint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lesson files were served inline (no Content-Disposition) → browsers
opened PDFs in a new tab instead of downloading; flaky across
browsers/providers. Set Content-Disposition: attachment with the
human-readable filename (RFC 5987 for Cyrillic) on upload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- /api/internal/grant: secret-auth endpoint for payment-router to
provision access on a paid order — find-or-create user (emailVerified,
temp password, mustChangePassword), enroll by course slug list,
AccessLog, delivery email. Idempotent by order_id.
- User.mustChangePassword flag (+ migration); (student) layout redirects
flagged users to /change-password (forced first-login change).
- email.ts: sendCourseGrantEmail (temp password for new buyers).
- middleware: /api/internal is public (own secret auth).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Better Auth does not auto-resend on 403 (verified against Resend logs),
so request a fresh link via authClient.sendVerificationEmail before
telling the user a new email was sent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
requireEmailVerification was enabled but no sendVerificationEmail was
configured — new users saw "check your email", never received anything,
and couldn't log in (403 shown as "wrong email or password").
- email.ts: add verification email template; welcome email no longer
claims the account is "confirmed"
- auth.ts: configure emailVerification (send on signup, resend on
unverified login attempt, auto sign-in after verification, 24h TTL)
- register route: callbackURL=/dashboard so the verify link lands in
the cabinet
- login form: distinguish 403 (unverified — tell user a fresh link was
sent) and 429 (rate limit) from wrong credentials
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Backend: explicitly return 409 EMAIL_TAKEN when the email already
exists, instead of Better Auth's silent 200 (common after migration).
- Frontend: split error handling — network failure, non-JSON body
(502/proxy), and meaningful server errors get distinct messages; the
"email already registered" case shows login / reset-password links
instead of a generic "connection error".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract ImpersonateButton into a shared component and reuse it on the
admin user profile page (header, hidden for admins) — same behaviour as
the users list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Quick-enroll modal now takes access duration in days (0/empty = unlimited)
and shows the computed expiry date, instead of a manual date picker.
- Dashboard upsell no longer shows the "Всё включено" bundle of a course
the student already owns in its base edition (obsidian/zotero).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Show the sellable course lineup on the dashboard: courses the student
doesn't own render as blurred cards with a lock and a CTA linking to the
product landing page (new tab). Owned courses are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add admin auth guard (redirect to /dashboard if not admin)
- Add delete-action.ts with deleteComment(commentId) soft-delete action
- Fix pagination label to show "Страница X из Y · Всего: N"
- Existing actions.ts, comments-table.tsx, and admin-nav entry preserved
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace client HomeworkFilters component with server-side <form method="GET">
- Switch search param from `search` to `q`, add lesson title to search scope
- Change status filter from DB status field to feedback-count logic (pending=no feedbacks, reviewed=has feedbacks)
- Update pagination label to "Страница X из Y · Всего: N"
- Preserve all existing submission links and layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "+ Доступ" button to each user row in the admin users table.
Clicking it opens a centered modal with a course dropdown, optional
expiry date, and a Server Action that upserts CourseEnrollment, logs
to AccessLog, and sends sendCourseAccessEmail on new enrollments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add emailVerified filter (true/false/any) to UsersSearch component
- Wire emailVerified param through page searchParams, where clause, and pageUrl helper
- Preserve emailVerified in pagination links alongside existing search/role/balance params
- Update pagination label to "Страница X из Y · Всего: N"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace non-existent --surface/--surface-muted/--border-strong with actual
design-system tokens (--color-surface, --background, --foreground, --muted)
- Remove tmp/ segment from S3 upload key in question-upload route
- Add auto-scroll to bottom on new message in QuestionThread
- Block Send while file upload is in progress (uploading guard)
- Replace <a> with Next.js <Link> in new question page back-link
- Replace hardcoded #c00 error color with var(--destructive) in both files
- Replace hardcoded #E8E8E0/#F5F5F0 hex backgrounds with CSS tokens