mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
11 KiB
11 KiB
Frontend Code Style & Test Coverage
- Comments: Follow the code comment rules in
code-comments.md. - Tests: Test new JS functions (including helpers) and new Vue components whenever practical; update existing tests when behavior changes. When a unit test is impractical (DOM-heavy flows, third-party widget integration), the doc comment is still mandatory — it's the minimum bar.
- State: Shared state lives in reactive singleton modules under
src/common/andsrc/app/(e.g.,app/session.js,common/config.js,common/clipboard.js,common/log.js) that export areactive()/ref()object directly; components access them viaimportor via the globally-installed$config/$sessionplugins. Do not introduce Vuex, Pinia, or new ad-hoc stores — extend an existing singleton or add a new one alongside its peers incommon/orapp/. - Vue/Vuetify: Use the Options API in Vue components (consistent with the rest of the codebase); do not introduce Composition API or
<script setup>. - TypeScript: Do not introduce TypeScript. The frontend is a pure JS + Vue SFC codebase: no
.tsfiles, notsconfig.json, no<script lang="ts">blocks. JSDoc type annotations in comments are fine; full TS migrations are out of scope.
Frontend Formatting
- ESLint + Prettier own formatting. After edits run
make fmt-js(ornpm run fmtinsidefrontend/) andmake lint-jsto verify;frontend/eslint.config.mjsis the flat-config source of truth. - The dev container preinstalls
eslintandprettieron the globalPATHat the same versionfrontend/package.jsonpins (eslint --versionshould match theeslintentry infrontend/package.json). Invoke them directly (e.g.eslint --fix tests/) fromfrontend/— no need fornpx, which adds a spawn step and an extra resolution layer. - Prettier reflow is not part of
make fmt-js/eslint --fix— theprettier/prettierrule is set to"off"so intentional newlines (multi-line method chains, vertical predicate lists) are preserved. Runprettier --write <file>explicitly when a full reflow is wanted; do not run it blanket acrosssrc/ortests/. - Prettier uses
printWidth: 160, double quotes, semicolons,trailingComma: "es5", andproseWrap: "never"(seefrontend/.prettierrc.json). Do not hand-wrap long lines — let Prettier decide. CSS/SCSS usetabWidth: 4. - The repo-root
.editorconfigcovers indentation and newline style; don't override it locally. - Vue SFC block order is
<template>→<script>→<style>; keep it consistent with existing components.
Frontend Dependencies & Pins
frontend/README.mdis the canonical doc for pin rationale, theoverrideslayer, ESM-only upgrade blockers, and the orphan-audit pattern — read it before bumping any non-caret pin or adding/removing a top-level dep.- Pins are intentional. When a version has no caret (e.g.,
"axios": "1.16.1","vuetify": "3.12.2"), checkfrontend/README.mdandgit log -p -S "<pkg>" -- frontend/package.jsonfor the reason before changing it. - npm is a workspace; run
npm install --ignore-scripts --no-audit --no-fund --no-update-notifierfrom the repo root (notfrontend/) so the rootpackage-lock.jsonupdates. After dep changes also runmake audit,make build-js,make test-js, andmake notice. - Before adding a new dep or removing one as "unused", run
rg -nF "<pkg>" frontend ...plusnpm ls <pkg> --allto confirm there's no transitive consumer or peer-dep. Recent precedents:postcss-url,@vitejs/plugin-react,cheerio,@testing-library/react,vite-tsconfig-paths(all true orphans removed once consumer left).
Frontend Linting & Test Entry Points
- Follow the lint/format scripts in
frontend/package.json; all added JS, Vue, and tests must conform. - Unit tests (Vitest):
make test-js,make vitest-watch,make vitest-coverage. Acceptance:acceptance-*targets in the rootMakefile. - Always invoke Vitest through the npm/make wrapper, never bare
npx vitest run.frontend/package.json'stestscript wraps the call incross-env TZ=UTC BUILD_ENV=development NODE_ENV=development BABEL_ENV=test. Without those env vars ~50 component tests (Vuetify renders, chip-selector, login, location-input, batch-edit, people-tab, lightboxtoggleSidebar, etc.) and TZ-sensitive date tests fail spuriously — the failures look real but only reproduce in the unwrapped invocation. Do not compare a "failed N, passed M" report from barenpx vitest runagainst amake test-jsbaseline. For ad-hoc filtering on a single file, mirror the env explicitly:(cd frontend && TZ=UTC BUILD_ENV=development NODE_ENV=development BABEL_ENV=test npx vitest run <path>). - One-off TestCafe (single case by
testID):
Always return to repo root beforemake storage/acceptance make acceptance-sqlite-restart make wait-2 (cd frontend && npm run testcafe -- "chrome --headless=new --use-gl=angle --use-angle=swiftshader --disable-features=LocalNetworkAccessChecks" --config-file ./testcaferc.json --test-meta mode=public,type=short,testID=components-001 "tests/acceptance") make acceptance-sqlite-stopmake acceptance-sqlite-stop.
Frontend Test Gotchas
- Hidden-route UI checks under
/library/hiddenor/portal/hiddenrequire bothfiles.file_errorandphotos.photo_quality = -1;file_erroralone will not surface the row.
Playwright MCP Usage
- Endpoint
http://localhost:2342/; logins at/library/login(CE/Plus/Pro) and/portal/login(Portal). Use local compose admin credentials; if login fails, inspect the active compose env. - Viewports: desktop
1280x900; mobile uses the mobile Playwright server at375x667. Close the browser tab after scripted interactions. - Prefer waits over sleeps; click only visible/enabled elements; use role/label/text selectors (not XPath).
- Screenshots: small and reproducible — JPEG, visible viewport, deterministic
.local/screenshots/<case>/<step>__<viewport>.jpgnames, no large inline screenshots. - If
npxfetches an MCP server at runtime, add--yesor preinstall to avoid prompts. - Delegate to the
ui-testersubagent for any flow with more than ~2 browser steps (login + navigate + assert, multi-step forms, regression sweeps). Brief it with the URL, credentials, exact steps, and the verdict format you want back; ask for a short report so raw snapshots and console dumps stay out of the parent context. Drive Playwright MCP inline only for one-shot checks (single navigate, single screenshot).
Frontend Focus Management
- Dialogs must follow the shared pattern in
frontend/src/common/README.md: exposeref="dialog"on<v-dialog>, call$view.enter/leavein@after-enter/@after-leave, and avoid positivetabindex. - Persistent dialogs (
persistentprop) must handle Escape via@keydown.esc.exactto suppress Vuetify's rejection animation; keep other shortcuts on@keyupso inner inputs can cancel first. - Global shortcuts go through
onShortCut(ev)incommon/view.js, which only forwards Escape andctrl/metacombos — don't rely on it for arbitrary keys. - When a dialog opens nested menus (e.g., combobox suggestions), confirm they work with the global trap; see the README for troubleshooting.
Frontend Translations
- Never hardcode locale strings in templates or scripts — every user-visible string MUST go through
$gettext/Tso it appears infrontend/src/locales/translations.pot. - Exception — standardized technical identifiers stay untranslated. Render protocol/acronym/identifier field labels and option values as literal strings, not via
$gettext: e.g.Client ID,Client Credentials,OIDC,UUID,Node UUID. Translating them adds catalog noise and risks ambiguous renderings (e.g. "Client" → German "Kunde"). Common English field names likeSite URLorAdvertise URLstay translated. - Share role/provider labels, not the selectable lists. Display names live once in
frontend/src/options/auth.js(Roles()/Providers()maps, withRoleOptions(keys, labelKey)/ProviderOptions(keys, labelKey)builders). Private editions (plus/pro/portal) import these and pass their own key list — the selectable sets legitimately differ per edition (cluster_admin, LDAP/AD, reduced Plus set) but the labels must not be re-listed. - Extraction source of truth: root
make gettext-extract(viascripts/gettext-extract.sh), which scansfrontend/srcplus available overlays inplus/frontend,pro/frontend,portal/frontend. Feature PRs do not commit the resulting.pot/.pochurn — Weblate re-extracts on its refresh cycle (seespecs/frontend/translations.md); a dedicated "Regenerate translation catalogs" commit is the exception. - Avoid punctuation-only gettext keys (e.g.
$gettext("—")) — they clutterfrontend/src/locales/translations.pot. - Catalog integrity gate: run
make gettext-lint(scripts/gettext-lint.mjs) after any.po/.potedit. It flags placeholder-set mismatches betweenmsgidandmsgstr(frontend%{name}, backend printf verbs), edge-whitespace drift, andmsgfmt -cc-format fatals — defects that silently break variable substitution at runtime. Leading/trailing/internal whitespace inside amsgidis itself runtime-inert (vue3-gettexttrims + collapses keys on load and lookup), so trailing-space findings are cleanliness, not correctness. - Trimming a source string's trailing space must stay consistent across template +
.po+.pot: trim the literal, thenmake gettext-extract. Caveat:msgmerge --no-fuzzy-matchingtreats the trimmedmsgidas new, blanks itsmsgstr, and moves the old translation to an obsolete#~block — so carry that translation onto the active trimmed entry (collapse any folded multi-line#~ msgstr) beforemake gettext-compile, or every locale renders untranslated. This only fillsmsgstr, so Weblate merges it cleanly.
Web Templates & Shared Assets
- HTML entrypoints live in
assets/templates/:index.gohtml,app.gohtml,app.js.gohtml,splash.gohtml.assets/static/js/browser-check.jsruns capability checks before the main bundle; keep it loaded before the bundle script inapp.js.gohtmland don't adddefer/asyncto the bundle tag unless you reintroduce a guarded loader. - OIDC login completion bridges through
assets/templates/auth.gohtml, writing the session into namespaced browser storage — must stay aligned withfrontend/src/common/session.js,frontend/src/common/storage.js, and the login-form toggle infrontend/src/page/auth/login.vue. - When touching session bootstrap, verify
session.jsresolvesstorageNamespacefrom the real client-config shape (window.__CONFIG__/config.values), not just mocks. Add a focused test that would fail if restore fell back topp:root:. - The loader partial is reused in
pro/,plus/, andportal/assets/templates/index.gohtml; verify they still include it wheneverapp.js.gohtmlor bundle loading changes. - Splash styles:
frontend/src/css/splash.css— add new splash elements there for cross-edition consistency. - Browser baseline: Safari 13 / iOS 13 or current Chrome, Edge, Firefox.