- /metrics is bearer-protected via METRICS_TOKEN; the endpoint carries
authorization.credentials (pargar-app/metrics-token) so Prometheus
scrapes it the same way the live probe does.
- disabled by default: plain 'helm template' renders byte-identical;
values-apollo enables it (enabled: true, interval 30s already present).
- kube-prometheus (ns infra) selects ServiceMonitors in all namespaces;
its operator holds cluster-wide secret read to resolve the credential.
randAlphaNum fallbacks silently minted new db/redis/rustfs passwords on
every cluster-less render (always the case under Argo CD's helm
template), overwriting live credentials. Fresh installs now get a clear
error demanding explicit values; helm upgrades are unaffected (lookup
preserves existing secrets).
Bare string items rendered an invalid Deployment spec (LocalObjectReference
is a struct) which made Argo CD's strategic-merge patch fail with
'cannot restore struct from: string' on both Deployments.
download.docker.com resets intermittently from the cluster network, and
/root/.docker/cli-plugins is not searched when the workflow sets
DOCKER_CONFIG=/tmp/.docker ('Docker buildx is required').
- jobs on the apollo runner use act-job:20 (.gitea/job-image: node 20 +
docker CLI + buildx in system plugin paths) - no runtime downloads
- other runners fall back to installing, now into a system plugin path
The job runs in node:20-bookworm which ships no docker binary
(run 21 died with 'Unable to locate executable file: docker').
Install static docker client + buildx, and fail fast with an
actionable error if the runner has not mounted the docker socket.
- ci: run build jobs on the runner's default linux-amd64 label
- deploy: pin apollo images to 0.3.6 (built+pushed by CI on this tag)
Same app code as v0.3.5; first release deployed via Gitea Actions
(build) + Argo CD (rollout) instead of manual build/import/helm.
- Certificate: Hardcode approval gate — completely removed the auto-issue path;
`certRequiresApproval()` now strictly returns `true`, ignoring DB configuration.
- Certificate: Quiz completion now queues a pending record and triggers a
"Pending approval" notification instead of falsely confirming issuance.
- Certificate: Public endpoints now enforce a guard on `status=issued`; frontend
strips the auto-issue side effect and claim button (issuance restricted to admins).
- Chats tab: Refactor layout into a full-width centered banner above two
equal-height side-by-side panels (`grid-cols-1 lg:grid-cols-2`), utilizing
`max-h` and `overflow-y-auto` to eliminate whitespace bleed.
- Video player: Fix settings flyout prematurely closing on mouse movement (removed
`setSettingsOpen` from `poke`); add a click-outside listener via `mousedown`.
- Video player: Deduplicate controls — the speed pill now directly cycles playback
speed, leaving the gear icon as the single entry point for settings (eliminating
overlapping popovers).
- Video player: Implement full-bleed theater mode using the `-mx-[50vw] w-screen`
technique, keeping the progress strip constrained to `max-w-5xl` below.
Viewport:
- Add `viewport` export with `viewport-fit=cover` (enables CSS safe-area-inset-*
on bezel-less iOS/Android) and `interactive-widget=resizes-content` (prevents
layout jumps from virtual keyboard open/close)
iOS zoom prevention:
- input.tsx, textarea.tsx: change text-sm (14px) → text-base sm:text-sm (16px)
on mobile; iOS Safari auto-zooms any input with font-size < 16px
Touch targets:
- button.tsx: default h-10 → h-11 (44px), sm h-8 → h-9 (36px), icon h-10→h-11
matching the 44×44px iOS HIG and WCAG 2.5.5 minimum
- Add touch-manipulation to buttons, inputs, textarea to eliminate 300ms tap delay
Dialog → mobile bottom sheet:
- On mobile (< 640px): anchored to bottom with full width, rounded top corners,
slide-in-from-bottom / slide-out-to-bottom animation, drag handle affordance,
max-h-[90svh] + overflow-y-auto to prevent viewport overflow
- Safe area padding: pb-[max(1.25rem,env(safe-area-inset-bottom))]
- On desktop (sm+): unchanged centered modal with zoom animation
- Close button gets a 32×32 tap area on mobile
Mobile navigation:
- New bottom-nav.tsx: persistent 3-tab bottom bar (مسیر/مرکز/لیگ) shown below
md: breakpoint, hidden on desktop; 56px height + safe-area-inset-bottom;
active tab gets heavier stroke and scale
- Only shown for authenticated non-admin users
- app-shell.tsx: injects BottomNav; main content gets dynamic padding-bottom to
clear the bar (56px + safe area) on mobile, reverts to py-10 on md:
- Footer: safe-area-inset-bottom padding so content clears iPhone home indicator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task 4 — Event Registration Confirmation (glassmorphism removal):
- Replace translucent gradient + backdrop-blur-md toast in use-realtime.tsx
with a solid bg-card container, full-opacity border, and a colored
left accent bar — high contrast, readable in all themes, no blur
Task 5 — Video player UX (within anti-cheat constraints):
- Keyboard shortcuts: Space (play/pause), M (mute), ← (seek back 10s),
F (fullscreen) — active globally, disabled when an input is focused
- Fullscreen button added to the control bar (F key shortcut)
- Volume slider (0–100%) replaces mute-only toggle; volume is persisted
to localStorage across sessions under key `pargar:volume`
- Buffer range visualized as a translucent white bar behind the playback
progress track so users see how much is buffered
- Exponential backoff auto-retry on video error: 1s, 2s, 4s … 30s cap;
retry count shown in error message; manual retry clears the timer
- Speed controls (0.75x–2x) are intentionally absent: the verified-watch
anti-cheat locks playbackRate=1; changing it would break the heartbeat
accumulation that gates quiz unlock and certification
- HLS/ABR requires backend FFmpeg transcoding; the player is structured
so swapping src=mp4 → src=.m3u8 + hls.js is a one-file change when
the backend pipeline is ready
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- safe-markdown: add `cancelled` flag to async useEffect; no more
setState after unmount when component is destroyed mid-import
- rich-editor: same cleanup for preview tab effect; also remove dead
`prose prose-sm max-w-none` classes (Tailwind Typography not installed)
and replace with explicit arbitrary-selector markdown styling that
actually renders using existing design tokens
- profile-certificate-slot: drop dead `ORDER_LABEL`, `order` variable,
and `physicalOrder` from `MineCertificateResponse`; physical orders
are disabled — these were leftover references causing lint noise
- learning-panel: change `issuingCert: boolean` → `issuingCertFor:
number | null` so only the targeted user's button shows a spinner;
a boolean shared across all users caused the wrong row to animate
when the admin switched selected users mid-request
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cast editor.storage to any for tiptap-markdown's getMarkdown (untyped extension storage)
- Replace setContent(value, false) with setContent(value, { emitUpdate: false }) for TipTap v3 API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Raise Traefik read timeout to 1h (compose dev/prod + k3s HelmChartConfig)
so admin video uploads up to 200MB no longer 502 mid-upload.
- Migration 0029: cascade watch/quiz history on lesson delete and unlink
dependent lessons, so removing a lesson or chapter stops 500ing.
- Add the Apollo cluster Helm overlay (values-apollo.yaml) for the deployed
environment.
- k3s Traefik chart config also enables access logs, cross-namespace
IngressRoutes, and fixes the Prometheus scrape port.
Bump appVersion/product.go/values tags to 0.2.1; chart version 0.2.3.
Fix OTel schema URL conflict that crashed the v0.1.3 backend at startup
when an OTLP endpoint is configured (resource.Merge merged
resource.Default() schema 1.43.0 with semconv/v1.24.0's schema URL).
Use resource.NewSchemaless for the service.name attribute.
SetupTracing merged resource.Default() (schema 1.43.0 under otel 1.45.0)
with semconv/v1.24.0's schema URL, so resource.Merge errored with
'conflicting Schema URL' and the backend exited at startup whenever an
OTLP endpoint was configured. resource.NewSchemaless keeps service.name
without pinning a schema, surviving otel major bumps.
Keep Next on the patched 15.5 line instead of the Next 16 Dependabot bump, and move OpenTelemetry and gRPC to the versions that close the open Go alerts.
Shared NAT egress makes client IP unusable for uniqueness checks. Gate the
behavior behind REGISTRATION_ENFORCE_IP (default false) for public/invite signup.