Polish product artwork and branding
18
HISTORY.md
|
|
@ -183,7 +183,7 @@
|
||||||
|
|
||||||
### Release Image
|
### Release Image
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -301,7 +301,7 @@
|
||||||
|
|
||||||
### Release Image
|
### Release Image
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -433,7 +433,7 @@
|
||||||
|
|
||||||
### Release Image
|
### Release Image
|
||||||
|
|
||||||

|

|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.35.1
|
## v0.35.1
|
||||||
|
|
@ -448,7 +448,7 @@
|
||||||
|
|
||||||
### Release Image
|
### Release Image
|
||||||
|
|
||||||

|

|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.35.0
|
## v0.35.0
|
||||||
|
|
@ -467,7 +467,7 @@
|
||||||
|
|
||||||
### Release Image
|
### Release Image
|
||||||
|
|
||||||

|

|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.34.3
|
## v0.34.3
|
||||||
|
|
@ -516,7 +516,7 @@
|
||||||
|
|
||||||
### Release Image
|
### Release Image
|
||||||
|
|
||||||

|

|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.34.2
|
## v0.34.2
|
||||||
|
|
@ -806,7 +806,7 @@
|
||||||
|
|
||||||
### Release Image
|
### Release Image
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## v0.28.0
|
## v0.28.0
|
||||||
|
|
||||||
|
|
@ -821,7 +821,7 @@
|
||||||
- **Login device details** — Login history now stores parsed browser, OS, device type, and a short privacy-preserving device fingerprint hash derived from user-agent plus coarse IP prefix. The Profile login history dialog shows the richer device details to help users recognize their own sign-ins.
|
- **Login device details** — Login history now stores parsed browser, OS, device type, and a short privacy-preserving device fingerprint hash derived from user-agent plus coarse IP prefix. The Profile login history dialog shows the richer device details to help users recognize their own sign-ins.
|
||||||
- **Profile last-login card** — The Profile summary now shows the most recent login date, IP address, device type, browser, and OS inline. Clicking the modern last-login card opens the full login history modal with device IDs and recent sign-in details.
|
- **Profile last-login card** — The Profile summary now shows the most recent login date, IP address, device type, browser, and OS inline. Clicking the modern last-login card opens the full login history modal with device IDs and recent sign-in details.
|
||||||
- **Public privacy page** — Added public `/api/privacy` and `/privacy` pages with a modern, scan-friendly policy layout, plus an About page Privacy button. The Profile login-history modal now notes that login device details are shown only to the user and are not shared with admins in the app UI.
|
- **Public privacy page** — Added public `/api/privacy` and `/privacy` pages with a modern, scan-friendly policy layout, plus an About page Privacy button. The Profile login-history modal now notes that login device details are shown only to the user and are not shared with admins in the app UI.
|
||||||
- **Release notes image support** — Release notes now render Markdown image blocks in a centered responsive frame, and the in-app release notes dialog displays the `doingmypart.jpg` release image at the end of the v0.28.0 entry.
|
- **Release notes image support** — Release notes now render Markdown image blocks in a centered responsive frame, and the in-app release notes dialog displays branded release art at the end of the v0.28.0 entry.
|
||||||
- **Backend-controlled update card reset** — The “What’s new” card now relies only on the backend `last_seen_version` check. `/api/auth/me` exposes the active `release_notes_version`, acknowledging an update stores that version, and any future package-version update will automatically show the card again for users who have not seen it.
|
- **Backend-controlled update card reset** — The “What’s new” card now relies only on the backend `last_seen_version` check. `/api/auth/me` exposes the active `release_notes_version`, acknowledging an update stores that version, and any future package-version update will automatically show the card again for users who have not seen it.
|
||||||
- **Instant bill search and filters** — Bills and Tracker now include search inputs for quickly finding bills by name, category, notes, or amount. Bills adds category, billing-cycle, autopay, due-bucket, debt, and inactive filters; Tracker adds current-month category, billing-cycle, unpaid, overdue, autopay, due-bucket, and debt filters.
|
- **Instant bill search and filters** — Bills and Tracker now include search inputs for quickly finding bills by name, category, notes, or amount. Bills adds category, billing-cycle, autopay, due-bucket, debt, and inactive filters; Tracker adds current-month category, billing-cycle, unpaid, overdue, autopay, due-bucket, and debt filters.
|
||||||
- **Command palette bill lookup** — Added a global Ctrl+K command palette for finding bills by name, category, notes, due details, or amount, with quick jumps into Bills or the current Tracker search.
|
- **Command palette bill lookup** — Added a global Ctrl+K command palette for finding bills by name, category, notes, due details, or amount, with quick jumps into Bills or the current Tracker search.
|
||||||
|
|
@ -858,7 +858,7 @@
|
||||||
|
|
||||||
### Release Image
|
### Release Image
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## v0.27.04
|
## v0.27.04
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input';
|
||||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
||||||
import { FieldRow, Toggle } from './adminShared';
|
import { FieldRow, Toggle } from './adminShared';
|
||||||
|
|
||||||
const AUTHENTIK_ICON_URL = '/img/auth.png';
|
const OIDC_ICON_URL = '/img/auth.svg';
|
||||||
|
|
||||||
interface OidcForm {
|
interface OidcForm {
|
||||||
local_login_enabled: boolean;
|
local_login_enabled: boolean;
|
||||||
|
|
@ -248,7 +248,7 @@ export default function AuthMethodsCard() {
|
||||||
<div className="space-y-4 pt-2 border-t border-border">
|
<div className="space-y-4 pt-2 border-t border-border">
|
||||||
<div className="flex items-center gap-2 pt-1 text-sm font-medium text-muted-foreground">
|
<div className="flex items-center gap-2 pt-1 text-sm font-medium text-muted-foreground">
|
||||||
<img
|
<img
|
||||||
src={AUTHENTIK_ICON_URL}
|
src={OIDC_ICON_URL}
|
||||||
alt=""
|
alt=""
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="h-5 w-5 shrink-0 object-contain"
|
className="h-5 w-5 shrink-0 object-contain"
|
||||||
|
|
@ -466,7 +466,7 @@ export default function AuthMethodsCard() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={AUTHENTIK_ICON_URL}
|
src={OIDC_ICON_URL}
|
||||||
alt=""
|
alt=""
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="mr-2 h-4 w-4 shrink-0 object-contain"
|
className="mr-2 h-4 w-4 shrink-0 object-contain"
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,6 @@ export default function LoginPage() {
|
||||||
const localEnabled = authMode?.local_enabled !== false;
|
const localEnabled = authMode?.local_enabled !== false;
|
||||||
const oidcEnabled = !!authMode?.oidc_enabled && !!authMode?.oidc_login_url;
|
const oidcEnabled = !!authMode?.oidc_enabled && !!authMode?.oidc_login_url;
|
||||||
const providerName = authMode?.oidc_provider_name || 'authentik';
|
const providerName = authMode?.oidc_provider_name || 'authentik';
|
||||||
const isAuthentikProvider = providerName.toLowerCase().includes('authentik');
|
|
||||||
|
|
||||||
const handleChangePassword = async (e: React.FormEvent) => {
|
const handleChangePassword = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -455,17 +454,15 @@ export default function LoginPage() {
|
||||||
window.location.href = authMode?.oidc_login_url || '';
|
window.location.href = authMode?.oidc_login_url || '';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isAuthentikProvider && (
|
<img
|
||||||
<img
|
src="/img/auth.svg"
|
||||||
src="/img/auth.png"
|
alt=""
|
||||||
alt=""
|
aria-hidden="true"
|
||||||
aria-hidden="true"
|
className="h-5 w-5 object-contain shrink-0"
|
||||||
className="h-5 w-5 object-contain shrink-0"
|
onError={(e) => {
|
||||||
onError={(e) => {
|
(e.target as HTMLImageElement).style.display = 'none';
|
||||||
(e.target as HTMLImageElement).style.display = 'none';
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
Continue with {providerName}
|
Continue with {providerName}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 331 KiB |
|
|
@ -1,11 +1,27 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-labelledby="title desc">
|
||||||
<!-- Authentik brand mark — orange geometric shield/key icon -->
|
<title id="title">Bill Tracker secure access</title>
|
||||||
<path
|
<desc id="desc">A dark security shield with a green confirmation check and teal connection points.</desc>
|
||||||
fill="#FD4B2D"
|
<defs>
|
||||||
d="M12 2L4 5.5v6c0 4.25 3.4 8.2 8 9.5 4.6-1.3 8-5.25 8-9.5v-6L12 2z"
|
<linearGradient id="ink" x1="34" x2="222" y1="34" y2="222" gradientUnits="userSpaceOnUse">
|
||||||
/>
|
<stop offset="0" stop-color="#172025"/>
|
||||||
<path
|
<stop offset="1" stop-color="#0f1417"/>
|
||||||
fill="#fff"
|
</linearGradient>
|
||||||
d="M12 7a2.5 2.5 0 0 1 2.5 2.5c0 1-.6 1.88-1.5 2.3V14h-2v-2.2A2.5 2.5 0 0 1 9.5 9.5 2.5 2.5 0 0 1 12 7zm0 1.5a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-.75 5.75h1.5v1.5h-1.5v-1.5z"
|
<linearGradient id="trust" x1="62" x2="184" y1="190" y2="58" gradientUnits="userSpaceOnUse">
|
||||||
/>
|
<stop offset="0" stop-color="#1e9f93"/>
|
||||||
|
<stop offset="0.58" stop-color="#40c878"/>
|
||||||
|
<stop offset="1" stop-color="#96e6a1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<filter id="shadow" x="-18%" y="-16%" width="136%" height="140%" color-interpolation-filters="sRGB">
|
||||||
|
<feDropShadow dx="0" dy="12" stdDeviation="16" flood-color="#061014" flood-opacity="0.28"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="256" height="256" rx="58" fill="url(#ink)"/>
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<path d="M128 45 186 67v44c0 41-24 71-58 90-34-19-58-49-58-90V67l58-22Z" fill="none" stroke="url(#trust)" stroke-width="14" stroke-linejoin="round"/>
|
||||||
|
<path d="M101 126.5 120.5 146 162 93" fill="none" stroke="#96e6a1" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
|
||||||
|
<path d="M101 126.5 120.5 146 162 93" fill="none" stroke="#40c878" stroke-linecap="round" stroke-linejoin="round" stroke-width="8"/>
|
||||||
|
<circle cx="75" cy="182" r="13" fill="#23b6a8"/>
|
||||||
|
<circle cx="181" cy="182" r="13" fill="#23b6a8"/>
|
||||||
|
<path d="M88 182h80" stroke="#23b6a8" stroke-linecap="round" stroke-width="8" opacity="0.76"/>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 25 KiB |
|
|
@ -32,14 +32,14 @@ The visual lane is **premium calm**.
|
||||||
|
|
||||||
## Core Colors
|
## Core Colors
|
||||||
|
|
||||||
| Token | Hex | Use |
|
| Token | Hex | Use |
|
||||||
| --- | --- | --- |
|
| ----- | --------- | --------------------------------------------------- |
|
||||||
| Ink | `#101417` | Dark app shell, mark background, premium depth |
|
| Ink | `#101417` | Dark app shell, mark background, premium depth |
|
||||||
| Paper | `#f8faf9` | Light app background, high-contrast text on dark |
|
| Paper | `#f8faf9` | Light app background, high-contrast text on dark |
|
||||||
| Trust | `#40c878` | Primary actions, brand mark, success, active states |
|
| Trust | `#40c878` | Primary actions, brand mark, success, active states |
|
||||||
| Teal | `#23b6a8` | Bank sync, data flow, secondary brand accent |
|
| Teal | `#23b6a8` | Bank sync, data flow, secondary brand accent |
|
||||||
| Amber | `#d99a24` | Due soon, warnings, drift, review-needed states |
|
| Amber | `#d99a24` | Due soon, warnings, drift, review-needed states |
|
||||||
| Rose | `#df4661` | Overdue, destructive, high urgency |
|
| Rose | `#df4661` | Overdue, destructive, high urgency |
|
||||||
|
|
||||||
The app implementation maps these through OKLCH theme tokens in `client/index.css`; do not hardcode the hex values in components unless creating static brand assets.
|
The app implementation maps these through OKLCH theme tokens in `client/index.css`; do not hardcode the hex values in components unless creating static brand assets.
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ Primary source files live in `client/public/brand/`.
|
||||||
|
|
||||||
- `lockup.svg` - primary logo for dark surfaces.
|
- `lockup.svg` - primary logo for dark surfaces.
|
||||||
- `lockup-light.svg` - primary logo for light surfaces.
|
- `lockup-light.svg` - primary logo for light surfaces.
|
||||||
- `logo.png` - raster export of the primary logo for docs and non-SVG contexts.
|
- `logo.png` - light-surface raster export of the primary logo for docs and non-SVG contexts.
|
||||||
- `mark.svg` - compact app mark and favicon.
|
- `mark.svg` - compact app mark and favicon.
|
||||||
- `mark.png` - raster export of the compact app mark.
|
- `mark.png` - raster export of the compact app mark.
|
||||||
- `mark-mono.svg` - single-color mark for constrained contexts.
|
- `mark-mono.svg` - single-color mark for constrained contexts.
|
||||||
|
|
@ -59,6 +59,11 @@ Primary source files live in `client/public/brand/`.
|
||||||
- `pwa-192.png`, `pwa-512.png`, `pwa-maskable.svg`, `pwa-maskable-512.png`, `apple-touch-icon.png` - install surfaces.
|
- `pwa-192.png`, `pwa-512.png`, `pwa-maskable.svg`, `pwa-maskable-512.png`, `apple-touch-icon.png` - install surfaces.
|
||||||
- `glyphs/*.svg` - standalone product glyph source files.
|
- `glyphs/*.svg` - standalone product glyph source files.
|
||||||
|
|
||||||
|
Supporting app artwork:
|
||||||
|
|
||||||
|
- `client/public/img/auth.svg` - branded secure-access glyph for OIDC/authentication surfaces.
|
||||||
|
- `client/public/img/auth.png` - raster fallback export of the secure-access glyph.
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
- Use the full lockup when the product needs to introduce itself.
|
- Use the full lockup when the product needs to introduce itself.
|
||||||
|
|
@ -70,15 +75,15 @@ Rules:
|
||||||
|
|
||||||
Bill Tracker has a small proprietary product glyph set for page identity only:
|
Bill Tracker has a small proprietary product glyph set for page identity only:
|
||||||
|
|
||||||
| Product area | Asset |
|
| Product area | Asset |
|
||||||
| --- | --- |
|
| ----------------- | ------------------------------------------------ |
|
||||||
| Tracker | `client/public/brand/glyphs/tracker.svg` |
|
| Tracker | `client/public/brand/glyphs/tracker.svg` |
|
||||||
| Bills | `client/public/brand/glyphs/bills.svg` |
|
| Bills | `client/public/brand/glyphs/bills.svg` |
|
||||||
| Calendar | `client/public/brand/glyphs/calendar.svg` |
|
| Calendar | `client/public/brand/glyphs/calendar.svg` |
|
||||||
| Analytics | `client/public/brand/glyphs/analytics.svg` |
|
| Analytics | `client/public/brand/glyphs/analytics.svg` |
|
||||||
| Data / Banking | `client/public/brand/glyphs/data-banking.svg` |
|
| Data / Banking | `client/public/brand/glyphs/data-banking.svg` |
|
||||||
| Snowball / Payoff | `client/public/brand/glyphs/snowball-payoff.svg` |
|
| Snowball / Payoff | `client/public/brand/glyphs/snowball-payoff.svg` |
|
||||||
| Admin / Security | `client/public/brand/glyphs/admin-security.svg` |
|
| Admin / Security | `client/public/brand/glyphs/admin-security.svg` |
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
|
|
@ -86,6 +91,7 @@ Rules:
|
||||||
- Keep Lucide icons for actions, controls, menus, buttons, tables, and common UI patterns.
|
- Keep Lucide icons for actions, controls, menus, buttons, tables, and common UI patterns.
|
||||||
- Never replace familiar action icons such as edit, delete, search, settings, upload, download, calendar navigation, or logout.
|
- Never replace familiar action icons such as edit, delete, search, settings, upload, download, calendar navigation, or logout.
|
||||||
- Glyphs should reuse the mark language: bars, checks, ledger lines, quiet rounded geometry.
|
- Glyphs should reuse the mark language: bars, checks, ledger lines, quiet rounded geometry.
|
||||||
|
- Avoid third-party-style or novelty images inside core app surfaces unless the product explicitly needs a partner logo.
|
||||||
|
|
||||||
## Typography
|
## Typography
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1222,7 +1222,11 @@ The lazy audit helper in `db/database.js` is:
|
||||||
let _logAudit = null;
|
let _logAudit = null;
|
||||||
function getLogAudit() {
|
function getLogAudit() {
|
||||||
if (!_logAudit) {
|
if (!_logAudit) {
|
||||||
try { _logAudit = require('../services/auditService').logAudit; } catch { _logAudit = () => {}; }
|
try {
|
||||||
|
_logAudit = require('../services/auditService').logAudit;
|
||||||
|
} catch {
|
||||||
|
_logAudit = () => {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return _logAudit;
|
return _logAudit;
|
||||||
}
|
}
|
||||||
|
|
@ -1255,7 +1259,7 @@ Creates the React root and wraps `App` with global providers. Theme is provided
|
||||||
### `client/App.tsx`
|
### `client/App.tsx`
|
||||||
|
|
||||||
- Creates a TanStack `QueryClient` with:
|
- Creates a TanStack `QueryClient` with:
|
||||||
- Global `QueryCache.onError` — toasts a `sonner` error when a *background* refetch fails while stale data is on screen. Pages render inline errors on the initial load so the toast only fires for refetches.
|
- Global `QueryCache.onError` — toasts a `sonner` error when a _background_ refetch fails while stale data is on screen. Pages render inline errors on the initial load so the toast only fires for refetches.
|
||||||
- `staleTime: 2 * 60 * 1000`, `retry: 1`, `refetchOnWindowFocus: false`.
|
- `staleTime: 2 * 60 * 1000`, `retry: 1`, `refetchOnWindowFocus: false`.
|
||||||
- Uses lazy loading and `Suspense` with `PageLoader` for most pages.
|
- Uses lazy loading and `Suspense` with `PageLoader` for most pages.
|
||||||
- Wraps route elements in `ErrorBoundary`; pages further wrap in `PageTransition`.
|
- Wraps route elements in `ErrorBoundary`; pages further wrap in `PageTransition`.
|
||||||
|
|
@ -1310,8 +1314,8 @@ Routes (all rendered through `Routes` with `ErrorBoundary`, lazy + `Suspense` fo
|
||||||
- Exposes grouped functions for `auth`, `admin`, `notifications`, `profile`, `tracker`, `calendar`, `summary`, `bills`, `payments`, `categories`, `settings`, `analytics`, `status`, `version`/`about`, `import`, `export`, `data-sources`, `transactions`, `matches`, `snowball`, `subscriptions`, `spending`, `monthlyStartingAmounts`, `csvImport`, `matchSuggestions`, `bankTransactionsLedger`, `matchTransaction`, `unmatchTransaction`, `ignoreTransaction`, `unignoreTransaction`, `applyTransactionMerchantMatch`, `autoCategorizeTransactions`, `categoryGroups`, `copySpendingBudgets`, `spendingIncome`, `spendingCategoryRules`, `createPaymentOrConfirm` (via `client/lib/paymentActions.ts`).
|
- Exposes grouped functions for `auth`, `admin`, `notifications`, `profile`, `tracker`, `calendar`, `summary`, `bills`, `payments`, `categories`, `settings`, `analytics`, `status`, `version`/`about`, `import`, `export`, `data-sources`, `transactions`, `matches`, `snowball`, `subscriptions`, `spending`, `monthlyStartingAmounts`, `csvImport`, `matchSuggestions`, `bankTransactionsLedger`, `matchTransaction`, `unmatchTransaction`, `ignoreTransaction`, `unignoreTransaction`, `applyTransactionMerchantMatch`, `autoCategorizeTransactions`, `categoryGroups`, `copySpendingBudgets`, `spendingIncome`, `spendingCategoryRules`, `createPaymentOrConfirm` (via `client/lib/paymentActions.ts`).
|
||||||
- File download/upload endpoints use raw `fetch` because responses/bodies are blobs or octet streams.
|
- File download/upload endpoints use raw `fetch` because responses/bodies are blobs or octet streams.
|
||||||
- **All 16 import sites** that referenced `@/api.js` were normalized to `@/api` so Vite/TS resolve the `.ts`.
|
- **All 16 import sites** that referenced `@/api.js` were normalized to `@/api` so Vite/TS resolve the `.ts`.
|
||||||
- **Track C response typing** — see §7 *Domain types* for the full list. In short, the high-traffic domains (`auth`, `bills`, `payments`, `categories`, `tracker`, `snowball`, `subscriptions`, `spending`, `bank-ledger`) all return typed responses via `get<T>` / `post<T>`; the remaining single-consumer non-money casts (adminUsers, version/about/privacy/health/importHistory) are deliberately left as-is.
|
- **Track C response typing** — see §7 _Domain types_ for the full list. In short, the high-traffic domains (`auth`, `bills`, `payments`, `categories`, `tracker`, `snowball`, `subscriptions`, `spending`, `bank-ledger`) all return typed responses via `get<T>` / `post<T>`; the remaining single-consumer non-money casts (adminUsers, version/about/privacy/health/importHistory) are deliberately left as-is.
|
||||||
- **Server error-shape consistency (Track D).** The global 500 handler in `server.js` emits `{ error, code: 'INTERNAL_ERROR' }` so an unexpected server error carries the same `{error, code}` field the client can switch on as every structured 4xx. See §5 *Response conventions*.
|
- **Server error-shape consistency (Track D).** The global 500 handler in `server.js` emits `{ error, code: 'INTERNAL_ERROR' }` so an unexpected server error carries the same `{error, code}` field the client can switch on as every structured 4xx. See §5 _Response conventions_.
|
||||||
|
|
||||||
### Domain types — `client/types.ts`
|
### Domain types — `client/types.ts`
|
||||||
|
|
||||||
|
|
@ -1514,7 +1518,7 @@ Forgejo Actions runs on every push and PR: `actions/checkout@v4` on `node:22-boo
|
||||||
### Vite (`vite.config.mjs`)
|
### Vite (`vite.config.mjs`)
|
||||||
|
|
||||||
- `@vitejs/plugin-react` with `babel-plugin-react-compiler` enabled (React 19 auto-memoization). The codebase is rules-of-hooks clean (enforced by `eslint-plugin-react-hooks`).
|
- `@vitejs/plugin-react` with `babel-plugin-react-compiler` enabled (React 19 auto-memoization). The codebase is rules-of-hooks clean (enforced by `eslint-plugin-react-hooks`).
|
||||||
- `vite-plugin-pwa` with `registerType: 'autoUpdate'`, manifest, icons in `client/public/img/`, runtime caching for `/api/(tracker|bills|calendar|summary|analytics|snowball|categories)` via `NetworkFirst` (5s network timeout, 30-entry max, 5-min max-age).
|
- `vite-plugin-pwa` with `registerType: 'autoUpdate'`, manifest, icons in `client/public/brand/`, runtime caching for `/api/(tracker|bills|calendar|summary|analytics|snowball|categories)` via `NetworkFirst` (5s network timeout, 30-entry max, 5-min max-age).
|
||||||
- `__APP_VERSION__` is injected at build time from `package.json`; the SPA reads it through `client/lib/version.ts`.
|
- `__APP_VERSION__` is injected at build time from `package.json`; the SPA reads it through `client/lib/version.ts`.
|
||||||
- Manual chunk splitting: `vendor-react` (react / react-dom / react-router-dom), `vendor-radix` (dialog / select / dropdown-menu / tabs / tooltip / alert-dialog), `vendor-motion` (framer-motion), `vendor-query` (@tanstack/react-query + @tanstack/react-virtual).
|
- Manual chunk splitting: `vendor-react` (react / react-dom / react-router-dom), `vendor-radix` (dialog / select / dropdown-menu / tabs / tooltip / alert-dialog), `vendor-motion` (framer-motion), `vendor-query` (@tanstack/react-query + @tanstack/react-virtual).
|
||||||
- `resolve.alias['@']` → `client/`, matches the Vite alias and the `tsconfig.json` `paths` mapping.
|
- `resolve.alias['@']` → `client/`, matches the Vite alias and the `tsconfig.json` `paths` mapping.
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 25 KiB |
BIN
img/auth.png
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 331 KiB |
BIN
img/logo.png
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 25 KiB |
BIN
img/logo_cut.png
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 25 KiB |
|
|
@ -36,6 +36,7 @@ export default defineConfig({
|
||||||
'brand/empty-state.svg',
|
'brand/empty-state.svg',
|
||||||
'brand/social-preview.svg',
|
'brand/social-preview.svg',
|
||||||
'brand/social-preview.png',
|
'brand/social-preview.png',
|
||||||
|
'img/auth.svg',
|
||||||
'brand/pwa-maskable.svg',
|
'brand/pwa-maskable.svg',
|
||||||
'brand/glyphs/tracker.svg',
|
'brand/glyphs/tracker.svg',
|
||||||
'brand/glyphs/bills.svg',
|
'brand/glyphs/bills.svg',
|
||||||
|
|
|
||||||