BatchImageGuideView carried ~140 user-visible strings outside vue-i18n:
hardcoded Chinese template strings (buttons, placeholders, tooltips,
table headers, dialog titles, empty states) plus an inline bilingual
batchImageText dictionary switched by isZhLocale() in script.
Move all of it into a new batchImage locale namespace (180 leaf keys
per language, zh/en verified key-aligned) registered in both locale
indexes, reusing 15 existing common.*/pagination.* keys. Column and
filter-option definitions become computed so language switches take
effect immediately; countable strings use vue-i18n plurals and the
three rich-text counters keep their inner bold spans via <i18n-t>
slots.
The Codex agent-instruction prompt template intentionally stays as a
plain string: it is an AI-facing prompt document containing literal
JSON braces that conflict with vue-i18n message syntax.
Verified: eslint, vue-tsc --noEmit, vite build, and the full vitest
suite (175 files / 1219 tests) all pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move the last few user-visible hardcoded strings into vue-i18n:
- AppHeader: "Toggle Menu" / "User Menu" aria-labels (screen readers
read these in English regardless of locale)
- CustomPageView: "Page not found" error body
- PaymentProviderDialog: display-name example placeholder hardcoded in
Chinese for all locales
Adds common.toggleMenu / common.userMenu / common.pageNotFound and
admin.settings.payment.customMethodDisplayNamePlaceholder to both zh
and en locales, and registers the new placeholder key in the
PaymentProviderDialog spec's i18n mock.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The project's dark theme uses the custom slate-based dark-* palette
(928 dark:bg-dark-* / 787 dark:border-dark-* occurrences), but a
minority of surfaces still used Tailwind's neutral gray-* scale, which
has a visibly cooler hue next to slate cards — most noticeably the
filter dropdown panels on the usage, accounts and subscriptions pages.
Migrate the remaining dark:bg-gray-* / dark:border-gray-* (and their
hover: variants) to the dark-* palette across 12 components: filter
dropdowns, chart card borders, menu separators, and small badges.
49 class-string substitutions, no structural or logic changes;
dark:text-gray-* is untouched since gray text on dark surfaces is the
established project-wide convention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The user info banner in UserBalanceModal renders on a dark:bg-dark-700
container, but the email line kept text-gray-900 and the balance line
text-gray-500 with no dark variants, leaving near-black text on a dark
background. Align both with the pattern already used elsewhere in the
same dialog (text-gray-900 dark:text-gray-100 / text-gray-500
dark:text-gray-400).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The available channels table rendered inside its own
"card overflow-hidden" wrapper instead of the ".table-wrapper" element
that TablePageLayout's scroll chain expects. The outer
.table-scroll-container is overflow-hidden, so when the channel list
grew taller than the viewport the content was simply clipped with no
scrollbar. The extra card wrapper also drew a double border inside the
layout's own card chrome.
Mount the table on .table-wrapper like every other table page so it
receives overflow-y-auto from the layout, and add a source-convention
regression test in the style of TablePageLayout.spec.ts.
Fixes#4555
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add prompt_audit_events.full_prompt (migration 182) so admins can review
the exact unredacted prompt that triggered a finding; blocking mode writes
it from the snapshot, async mode reconstructs it from the Redis scan
payload so jobs rows stay redaction-only
- Event detail API returns full_prompt (list endpoint stays lean); text is
NUL-stripped and capped at 65536 runes
- Detail dialog shows the full prompt in a scrollable pane with fallback to
the legacy redacted preview; page copy updated to match the new behavior
- Rework filter deletion into a dedicated dialog with time-range presets and
criteria-change preview invalidation; localize decision/risk/category
labels across the events workspace
- Fix pre-existing i18n message-compile spec by declaring the
@intlify/message-compiler dev dependency
Let admins configure private/intranet Guard endpoints without destination-class blocking, and fix prompt-audit switch layout so thumbs and labels no longer overlap.
Co-authored-by: Cursor <cursoragent@cursor.com>
Behind a reverse proxy (e.g. nginx with X-Real-IP), admin audit logs and
session IP/UA binding always recorded 127.0.0.1 because they hardcoded
the gin trusted_proxies chain, while API key IP restriction already
honored the "trust forwarded client IP" system setting.
- add ip.GetSecurityClientIP(c, trustForwarded) as the single source of
truth for security-sensitive client IP selection; API key auth
middlewares (main + google) refactored onto it with zero behavior change
- SessionBindingContext(cfg) now resolves the client IP via the same
toggle and injects it into the request context; token issuance,
binding enforcement and its mismatch audit record all read the
injected value, so issue/verify can never diverge
- audit log middleware and audit-log clear trace record the same
security client IP (middleware.SecurityClientIP), falling back to the
trusted proxy chain when the injection is absent
- settings UI hint (zh/en) documents the broadened toggle scope and the
one-time re-login after toggling while session binding is enabled
With the toggle off (default) behavior is byte-for-byte unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
- admin.accounts.oauth.openai.mobileRefreshTokenAuth was referenced by
OAuthAuthorizationFlow.vue since 9f8cffe88 (Mobile RT entry) but never
added to zh/en locales, rendering the raw key in the add-account wizard
- admin.accounts.oauth.openai.accessTokenAuth has the same latent issue
since 26060e702 (Sora AT import); currently hidden but fixed alongside
Co-Authored-By: Claude <noreply@anthropic.com>
Admins often recreate groups with the same pricing, routing, and account membership. A server-side duplicate creates an inactive copy for review, preserves eligible account priorities, and recovers ambiguous retries without creating extra groups.
Constraint: Group has no neutral JSON metadata field for durable operation recovery
Constraint: Model routing references account IDs, so copied configuration requires matching bindings
Rejected: Rebuild from the list response | it omits configuration and account priority details
Rejected: Store operation identity in business configuration | it would pollute real group settings
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep duplicated groups inactive until an administrator reviews the copied configuration
Tested: Go unit and full tests, go vet, integration-tag compile, frontend Vitest, lint, typecheck, production build, and Playwright duplicate flow
Not-tested: PostgreSQL container integration locally because Docker is unavailable; CI will execute the database-backed suite
Admins often recreate monitors with the same endpoint, model, and request settings. A server-side duplicate keeps the stored API key out of the browser, creates a disabled copy for review, and uses stable operation identity to recover ambiguous retries without creating extra rows.
Constraint: Stored monitor API keys must never be returned to the browser
Constraint: Applying a request template must preserve internal duplicate recovery metadata
Rejected: Rebuild the monitor from list data | list responses only contain a masked API key
Rejected: Copy runtime state and history | a duplicate should start as an unverified configuration
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep duplicated monitors disabled until an administrator reviews and enables them
Tested: Go unit tests for repository, service, and admin handler; integration-tag compile; go vet; golangci-lint v2.9; frontend Vitest, ESLint, typecheck, production build; Playwright duplicate flow
Not-tested: PostgreSQL container integration locally because Docker is unavailable; CI will execute the database-backed suite