# Koinkat Restructure Audit
**Date:** 2026-04-24
**Scope:** Read-only audit of the repo state before restructuring for public distribution.
**Repo root:** `C:\Users\marco\OneDrive\Desktop\Koinkat`
---
## 1. Files to Remove Entirely
### Must-delete (security / clearly junk)
Add these files to a folder called junk and add that to the .gitignore
| Path | Size | Reason |
|---|---|---|
| `KoinkatDemo/` | ~83 MB | Old Flask/SQLAlchemy prototype. Has its own nested `.git`, `.venv`, `__pycache__`, plus two oversized backup/temp markdown docs (210 KB + 144 KB). Already in `.gitignore` but still on disk. |
| `47e53ca9-ce28-4daa-82f6-c307ec7f516d.pem` | 3.2 KB | **Private key** at repo root (Enable Banking application key). `.gitignore` covers `*.pem` - verify with `git log --all -- '*.pem'` that it was never committed; if it was, **rotate at Enable Banking immediately**. |
| `957fc6d2-35c5-4f3f-ab60-216c3b3c427f.pem` | 3.2 KB | Second private key, same situation as above. |
| `KoinkatDemo/.env` | - | Contains plaintext Postgres password + two Flask secret keys (`SECRET_KEY`, `WTF_CSRF_SECRET_KEY`). Rotate those secrets on the assumption they are compromised, then delete with the folder. |
| `personal/` (contains `todo.md`) | - | Personal to-do list ("Ask people for feedback", "post it on LinkedIn"). Gitignored. |
| `.playwright-mcp/` | 80 KB | Stale browser captures + console log from a prior Playwright MCP session. Gitignored. |
### Strongly recommend deletion
| Path | Size | Reason |
|---|---|---|
| `.agents/` | 204 KB | Internal planning docs (`tasks/fix-app-wide-bugs.md` 19 KB, `tasks/new-features-plan.md` 44 KB, `docs/architecture.md` etc.). Not gitignored. Likely expose internal process, bug lists, and stale planning you don't want public. |
| `AGENTS.md` | 33 KB | Root-level agent-prompt / internal dev doc. Manual review recommended - often contains team conventions or stale references. |
| `dist/` | 1.6 MB | Vite build output, gitignored. Regenerates on every build. Safe to wipe from disk. |
| `.claude/settings.local.json` | - | Personal Claude Code local settings. Not ignored currently. |
| `.claude/skills/` | - | Personal Claude skills. Not ignored currently. |
### Additional files I think are useless / should be reviewed
| Path | Reason |
|---|---|
| `scripts/generate_mock.py` (37 KB) | Gitignored because of the blanket `scripts/` rule. If it's still needed to regenerate `src/mocks/eb_mock_fixtures.json`, un-ignore just this file. If it's one-shot output, delete. |
| `app-icon.png` (95 KB, repo root) | Looks like the source icon used to generate `src-tauri/icons/*`. Not broken, just loose at the root - consider moving to `src-tauri/icons/source/app-icon.png` or `docs/` for tidiness. Not a must-delete. |
| `docs/callback/` | Flag for review - confirm this is legitimate user-facing docs (likely the Enable Banking OAuth callback page). |
### Manual-review items (flagged for your decision)
- **`.env` files with real credentials** - only one exists (`KoinkatDemo/.env`, listed above). No Vite-facing `.env` files currently exist at the app root.
- Flask secrets in `KoinkatDemo/.env` - rotate then delete.
### `.gitignore` coverage gaps
Current `.gitignore` is good but has gaps worth closing once the cleanup is done:
- `.env.*` does **not** match a bare `.env`. Use `.env*` or both `.env` and `.env.*`.
- `.agents/` is not ignored.
- `.claude/settings.local.json` is not ignored.
- No `*.bak`, `*.old`, `*.backup`, `*.log` patterns.
- No editor/IDE swap patterns (`*.swp`, `*.swo`, `*~`, `.idea/`, `.vscode/`).
- `scripts/` is ignored wholesale, which is too aggressive if `generate_mock.py` is needed by contributors.
---
## 2. Mock & Sandbox Code Inventory
**Terminology note** - the codebase overloads two orthogonal concepts:
- **Mock mode** = build-time `VITE_EB_MOCK` flag. Swaps the real Enable Banking client for fixture-backed stubs. Pure developer harness - not a user-facing feature.
- **Sandbox mode** = runtime workspace type (`connection_type = 'sandbox'`, `is_demo_mode` column). A legitimate user feature - real users can connect to Enable Banking's sandbox environment.
These are independent. The `demo-service.ts` file despite its name belongs to **sandbox mode**, not **mock mode**.
### 2.1 Mock bank API
| File | Purpose | Imported by | Imports |
|---|---|---|---|
| `src/mocks/mock-enable-banking-service.ts` | Fixture-backed stub mirroring the real Enable Banking service (canned banks FinecoBank IT, Nordea DK, Barclays GB; sessions; balances; transactions; split hints). | `src/services/enable-banking-service.ts:23` - the **only** production touchpoint. | `./eb_mock_fixtures.json` |
| `src/mocks/eb_mock_fixtures.json` | Canned sessions / balances / transactions with split-parent/repayment sentinels. | `mock-enable-banking-service.ts` | - |
### 2.2 Sandbox workspace type
**Stays in the production build** - real feature for real users.
Type definitions:
- `src/types/enums.ts:7-8` - `BankEnvironment = 'sandbox' | 'production'`, `ConnectionType = 'manual' | 'sandbox' | 'linked'`.
- `src/types/models.ts:41-42` - `BANK_ENVIRONMENTS`, `CONNECTION_TYPES` readonly arrays.
Schema:
- `src/db/schema-v2.sql:28` - `CHECK (connection_type IN ('manual', 'sandbox', 'linked'))`.
- `src/db/schema-v2.sql:161, 206, 209` - `bank_connections.environment`, `is_demo_mode`.
- `src/db/schema.sql:157, 188, 191` - older schema equivalents.
Services / pages referencing sandbox:
- `src/services/api-config-service.ts:35, 41, 49, 61` - maps `environment === 'sandbox'` to `is_demo_mode` column.
- `src/services/koinkat-account-service.ts:96, 102` - sandbox/linked accounts get empty api_configs row.
- `src/pages/Connection.tsx:53, 166, 173, 197, 221, 255, 310-315, 343-358, 395-399, 630-632` - Sandbox card in the workspace creation UI (**this is the surface to gate in production**).
- `src/pages/Dashboard.tsx:62, 98-150` - `isBankDriven` check + `handleExitSandbox` + sandbox banner.
- `src/pages/Settings.tsx:25, 96, 97, 154` - 'Sandbox' badge.
- `src/stores/bank-store.ts:18, 52, 60` - Zustand store holds `isDemoMode`.
### 2.3 Demo service
| File | Purpose | Importers | Imports |
|---|---|---|---|
| `src/services/demo-service.ts` | Runtime helpers for the sandbox workspace feature: `isSandboxMode()`, `deactivateSandbox()`. **Production code, misleadingly named.** | `src/pages/Dashboard.tsx:30` (single importer, uses `deactivateSandbox`). `isSandboxMode()` currently has no callers. | `../db/database`, `./api-config-service`, `../lib/active-koinkat-account` |
Recommendation: consider renaming to `sandbox-service.ts` to remove the name collision with mock/demo concepts. Low priority.
### 2.4 Mock mode wiring (`VITE_EB_MOCK`)
Every reference across the codebase (docs excluded):
| # | File:Line | Role | What changes when flag is set |
|---|---|---|---|
| 1 | `src/vite-env.d.ts:4` | Type declaration | Declares `ImportMetaEnv.VITE_EB_MOCK?: string`. |
| 2 | `src/services/enable-banking-service.ts:40` | Dispatcher | `const IS_MOCK = import.meta.env.VITE_EB_MOCK === 'true';` Every exported function (`listBanks`, `verifyCredentials`, `startAuthorization`, `createSession`, `getBalances`, `getTransactions`, `getSessionStatus`, `deleteSession`) branches on `IS_MOCK` - delegates to `../mocks/mock-enable-banking-service` (normalizing snake_case to camelCase) or `./enable-banking-service-real`. |
| 3 | `src/pages/BankLink.tsx:60` | Page flag | `IS_MOCK_MODE` constant. |
| 4 | `src/pages/BankLink.tsx:65-69` | Page constant | `MOCK_CODE_MAP` (FinecoBank\|IT to mock-code-eur, etc.). |
| 5 | `src/pages/BankLink.tsx:163-195` (approx) | Flow gate | `handleConnect` skips the real OAuth redirect, synthesizes an `authId`, inserts a `bank_connections` row directly, then calls `handleAuthCallback`. |
| 6 | `src/pages/BankLink.tsx:540-551` | UI | Yellow InfoBanner: "Mock mode active - No real bank connection will be made…" |
| 7 | `src/pages/Review.tsx:27` | Page flag | `IS_MOCK_MODE` constant. |
| 8 | `src/pages/Review.tsx:508-519` | UI | InfoBanner flagging the two fixture split transactions. |
### 2.5 Debug routes
Router config: `src/App.tsx`. One `` parent route wraps every nested route.
**All routes defined:**
| Path | Component | In sidebar? |
|---|---|---|
| `/` (index) | `Dashboard` | Yes |
| `/review` | `Review` | Yes |
| `/transactions` | `TransactionList` | Yes |
| `/analysis` | `Analysis` | Yes |
| `/summary` | `Summary` | Yes |
| `/budgets` | `Budgets` | Yes |
| `/categories` | `Categories` | Yes |
| `/settings` | `Settings` | Yes (footer) |
| `/accounts/create`, `/accounts/:id/edit` | `AccountCreate`, `AccountEdit` | No (reached via Dashboard buttons) |
| `/transactions/create`, `/transactions/transfer`, `/transactions/:id/edit`, `/transactions/:id/split` | transaction forms | No (reached via Transactions list) |
| `/bank-link` | `BankLink` | No (reached via Settings/Dashboard) |
| `/budget-events` | redirect to `/budgets` | No (legacy redirect) |
| **`/rules`** | **`Rules`** | **No - explicitly hidden debug route** |
`src/components/layout/Sidebar.tsx:25-28` comment: *"The Rules page is intentionally NOT listed here. It lives at the `/rules` route for internal use (debugging the categorization engine, manual rule edits during development), but we don't want to expose it as a user-facing navigation target."*
The `Rules` page itself is functional (edits user/learned/MCC/system rules) - it's hidden from nav but reachable by typing `/rules` in the URL bar. See Section 6 Q1 for the decision on this.
Note: `src/pages/Connection.tsx` is NOT a route - Shell renders it directly when `view === 'accountHub'`.
### 2.6 Boundary summary (production to mock edges to gate for public build)
| # | From (production code) | To (mock code) | Action for prod build |
|---|---|---|---|
| E1 | `src/services/enable-banking-service.ts:23` `import * as mockService from '../mocks/mock-enable-banking-service'` | `src/mocks/mock-enable-banking-service.ts` | Remove import + dispatcher becomes thin re-export of `-real`. |
| E2 | `src/services/enable-banking-service.ts:40` `const IS_MOCK = ...` + every `if (IS_MOCK) { ... }` branch (lines 45-47, 55-56, 71-79, 94-114, 125-143, 164-202, 212-215, 222-225) | dispatcher branches | Remove. |
| E3 | `src/pages/BankLink.tsx:60` `IS_MOCK_MODE` | env | Remove. |
| E4 | `src/pages/BankLink.tsx:65-69` `MOCK_CODE_MAP` | fixture codes | Remove. |
| E5 | `src/pages/BankLink.tsx:163-195` mock branch in `handleConnect` | direct DB insert + fake callback | Remove entire `if` block. |
| E6 | `src/pages/BankLink.tsx:540-551` mock banner | UI | Remove. |
| E7 | `src/pages/Review.tsx:27` `IS_MOCK_MODE` | env | Remove. |
| E8 | `src/pages/Review.tsx:508-519` mock banner | UI | Remove. |
| E9 | `src/vite-env.d.ts:4` `VITE_EB_MOCK?: string` | typing | Remove (or move to dev-only `.d.ts`). |
| D1 | `src/App.tsx:18, 39` `/rules` route import + Route element | `src/pages/Rules.tsx` | Drop the import + Route tag. |
| S1 | `src/pages/Connection.tsx` Sandbox option in workspace creation (approx lines 310-358) | sandbox UI surface | Hide the Sandbox card/option in production builds. |
Mock code has no production-facing side effects - it only reads from `eb_mock_fixtures.json`, plus BankLink's mock branch writes a `bank_connections` row using the real DB. Removing the mock branch for production is safe.
---
## 3. Package.json Audit
### `dependencies`
| Package | Version | Category | Evidence |
|---|---|---|---|
| `@tauri-apps/api` | `^2` | **Unused (direct)** | 0 imports of `from '@tauri-apps/api'` in `src/`. Transitively satisfied by plugin packages. Safe to drop unless you add a direct `invoke()` call. |
| `@tauri-apps/plugin-deep-link` | `^2.4.8` | Production | `src/pages/BankLink.tsx:287` dynamic `import('@tauri-apps/plugin-deep-link')` |
| `@tauri-apps/plugin-dialog` | `^2` | Production | `src/pages/Connection.tsx:11` (`open as openDialog`) |
| `@tauri-apps/plugin-fs` | `^2` | Production | `src/pages/Connection.tsx:12` (`readTextFile`) |
| `@tauri-apps/plugin-http` | `^2` | Production | `src/lib/fx-fetch.ts:1`, `src/services/enable-banking-service-real.ts:2` |
| `@tauri-apps/plugin-shell` | `^2.3.5` | Production | `src/pages/BankLink.tsx:5` (`open`) |
| `@tauri-apps/plugin-sql` | `^2` | Production | `src/db/database.ts:1` |
| `big.js` | `^6.2.2` | Production | 11 files (money domain + formatters + services) |
| `date-fns` | `^4` | Production | 8 files (Settings, BankLink, services, Budgets, Dashboard) |
| `jose` | `^6.2.2` | Production | `src/services/enable-banking-service-real.ts:1` (JWT signing for EB auth) |
| `lucide-react` | `^0.468` | Production | 22 files (icons) |
| `react` | `^19` | Production | 17+ files |
| `react-dom` | `^19` | Production | `src/main.tsx:2` |
| `react-router` | `^7` | **Unused (direct)** | 0 imports of `from 'react-router'`. `react-router-dom@7` depends on it transitively. Safe to drop as a direct dep. |
| `react-router-dom` | `^7` | Production | 16 files |
| `recharts` | `^2` | Production | `src/pages/Budgets.tsx`, `Dashboard.tsx`, `Summary.tsx` |
| `zustand` | `^5` | Production | 5 store files under `src/stores/` |
### `devDependencies`
| Package | Version | Category | Evidence |
|---|---|---|---|
| `@tailwindcss/vite` | `^4` | Tooling (Vite plugin) | `vite.config.ts:3` |
| `@tauri-apps/cli` | `^2` | Tooling | `npm run tauri` |
| `@types/big.js` | `^6` | Tooling (type-only) | TS types |
| `@types/react` | `^19` | Tooling (type-only) | TS types |
| `@types/react-dom` | `^19` | Tooling (type-only) | TS types |
| `@vitejs/plugin-react` | `^4` | Tooling | `vite.config.ts:2` |
| `tailwindcss` | `^4` | Tooling | Peer of `@tailwindcss/vite`, used via `src/index.css` |
| `typescript` | `^5.7` | Tooling | `tsc` in `build` script |
| `vite` | `^6` | Tooling | `vite dev` / `vite build` |
### Summary
- **Direct-unused (candidates for removal):** `@tauri-apps/api`, `react-router`.
- **No mock-only dependencies.** The mock module uses only `./eb_mock_fixtures.json` - no external packages are attributable exclusively to mock/sandbox code.
- **No test framework installed** (no vitest/jest). If you want CI gates before public release, `vitest` is a natural fit.
---
## 4. Current Build Configuration
### 4.1 `.env*` files
Only one exists in the entire tree:
**`KoinkatDemo/.env`** (10 lines) - variable names only, no values:
`DATABASE_URL`, `APP_ENV`, `LOG_LEVEL`, `SECRET_KEY`, `WTF_CSRF_SECRET_KEY`, `SESSION_LIFETIME_MIN`, `CSRF_TIME_MIN`, `SESSION_COOKIE_SECURE`, `ENABLE_HSTS`.
**No Vite-level `.env` files exist** - `VITE_EB_MOCK` is set from the shell at dev time. There is no `.env.example`, `.env.development`, or `.env.production` for the Tauri app.
### 4.2 `import.meta.env.*` references
Grep across `src/`: **3 runtime reads + 1 type declaration**. All target `VITE_EB_MOCK`.
| File:line | Gating | What it controls |
|---|---|---|
| `src/vite-env.d.ts:4` | Type decl | Declares the env var for TypeScript. |
| `src/services/enable-banking-service.ts:40` | Function dispatch | Every EB export branches mock vs real. |
| `src/pages/BankLink.tsx:60` | Page flow + UI | Mock connect flow + mock-warning banner. |
| `src/pages/Review.tsx:27` | UI | Mock instructions banner. |
### 4.3 `package.json` scripts
| Script | Command | Behavior |
|---|---|---|
| `dev` | `vite` | Vite dev server on port 1420. Used by `tauri dev` via `beforeDevCommand`. |
| `build` | `tsc && vite build` | TS type-check/emit then production bundle into `dist/`. Used by `tauri build`. |
| `preview` | `vite preview` | Serves `dist/` locally for manual QA. |
| `tauri` | `tauri` | Thin passthrough to Tauri CLI. |
No `lint`, `test`, `typecheck`, or `format` scripts.
### 4.4 `vite.config.ts`
30 lines, static config (does not currently accept the `mode` argument).
- **Plugins:** `@vitejs/plugin-react`, `@tailwindcss/vite`.
- **`define`:** none.
- **`build.rollupOptions.output.manualChunks`:** splits `recharts` and `date-fns` into a `vendor-charts` chunk. Bundle-size only.
- **Mode-based conditionals:** none - single static config regardless of `dev` vs `production`.
- **Env-driven behavior:** reads `process.env.TAURI_DEV_HOST` for LAN mobile dev (HMR host).
- Port 1420, `strictPort: true`, watch ignores `src-tauri/**`, `clearScreen: false`.
### 4.5 `src-tauri/tauri.conf.json`
- `productName: "Koinkat"`, `identifier: "com.koinkat.app"`, `version: 0.1.0`.
- `build.frontendDist: "../dist"`, `build.devUrl: "http://localhost:1420"`, `beforeDevCommand: "npm run dev"`, `beforeBuildCommand: "npm run build"`.
- **No conditional / profile settings** - single static config.
- **No `bundle.*` metadata at all** (no icon, targets, publisher, copyright, category, short/long description, signing identity). **Not ready for public distribution as-is.**
- One window: 1280×800, min 900×600, centered.
- Plugins: `sql.preload: ["sqlite:koinkat.db"]`; `deep-link.desktop.schemes: ["koinkat"]`.
- **CSP** (strict baseline):
- `default-src 'self' tauri:` - good.
- `script-src 'self' tauri:` - no `unsafe-eval`/`unsafe-inline`. Good.
- `style-src 'self' 'unsafe-inline' https://fonts.googleapis.com` - inline needed for Tailwind v4; acceptable.
- `connect-src 'self' https://api.enablebanking.com https://cdn.jsdelivr.net https://latest.currency-api.pages.dev https://*.currency-api.pages.dev ipc: http://ipc.localhost` - three outbound origins. **Audit point:** `cdn.jsdelivr.net` may be unused - see Q10.
- `frame-ancestors 'none'; object-src 'none'; base-uri 'self'` - all good hardening.
### 4.6 `src-tauri/Cargo.toml`
Thin Rust shell - all real functionality lives in Tauri plugins.
| Crate | Purpose |
|---|---|
| `tauri-build` (build dep) | `build.rs` helper - embeds assets, generates capability files. |
| `tauri` v2 | Core runtime (webview host, IPC, window management). |
| `tauri-plugin-deep-link` | OS-level `koinkat://` URI handler for Enable Banking OAuth callback. |
| `tauri-plugin-sql` (sqlite feature) | Frontend DB access via `@tauri-apps/plugin-sql`. |
| `tauri-plugin-http` | Cross-origin HTTP from webview (FX fetch + EB real client). |
| `tauri-plugin-fs` | Sandboxed filesystem reads (PEM cert via `Connection.tsx`). |
| `tauri-plugin-dialog` | Native file-pick dialogs. |
| `tauri-plugin-shell` | `open()` URL in default browser for OAuth redirect. |
| `serde` (derive) + `serde_json` | IPC (de)serialization for any struct passed over IPC. |
| `tauri-plugin-single-instance` (mac/win/linux, deep-link feature) | Ensures one running instance; forwards `koinkat://` callbacks to the running instance. |
No direct `reqwest`, crypto, or database driver crates - all pushed to plugins or handled in JavaScript.
---
## 5. Proposed Three-Build Structure
### Target matrix
| Script | Vite mode | Mocks available | `/rules` route | Sandbox option in UI | App identifier | Use case |
|---|---|---|---|---|---|---|
| `npm run tauri dev` | `development` | Yes (if `VITE_EB_MOCK=true` in shell) | Visible | Visible | `com.koinkat.app.dev` (suggested) | Daily dev work |
| `npm run tauri build:demo` | `demo` | Yes (forced on by `.env.demo`) | Hidden | Visible | `com.koinkat.app.demo` (suggested) | Tutorial / marketing recordings only |
| `npm run tauri build` | `production` | **Impossible** (build fails if set) | Removed | Hidden | `com.koinkat.app` | Public distribution |
### Implementation
#### A. New env files (commit to repo)
**`.env.development`**
```
VITE_EB_MOCK=false
```
**`.env.demo`**
```
VITE_EB_MOCK=true
```
**`.env.production`**
```
VITE_EB_MOCK=false
```
**`.env.example`** (committed, for contributors)
```
# Set to 'true' in development to use fixture-backed Enable Banking mock client.
# Automatically forbidden in production builds.
VITE_EB_MOCK=false
```
#### B. Updated `package.json` scripts
```json
"scripts": {
"dev": "vite",
"build": "tsc && vite build --mode production",
"build:demo": "tsc && vite build --mode demo",
"preview": "vite preview",
"tauri": "tauri",
"tauri:dev": "tauri dev",
"tauri:build": "tauri build",
"tauri:build:demo": "tauri build --config src-tauri/tauri.conf.demo.json",
"typecheck": "tsc --noEmit",
"lint": "tsc --noEmit"
}
```
The demo build uses a second Tauri config that overrides `beforeBuildCommand` and the app identifier.
#### C. New `src-tauri/tauri.conf.demo.json`
```json
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Koinkat Demo",
"identifier": "com.koinkat.app.demo",
"build": {
"beforeBuildCommand": "npm run build:demo"
}
}
```
Tauri v2 merge-patches this over `tauri.conf.json` when passed with `-c`.
#### D. Updated `vite.config.ts`
```ts
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
import type { Plugin } from 'vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), 'VITE_');
const mocksAllowed = mode === 'development' || mode === 'demo';
const debugRoutesAllowed = mode === 'development';
const sandboxUiAllowed = mode === 'development' || mode === 'demo';
if (mode === 'production' && env.VITE_EB_MOCK === 'true') {
throw new Error('Build aborted: VITE_EB_MOCK=true is forbidden in production mode.');
}
const host = process.env.TAURI_DEV_HOST;
return {
plugins: [
react(),
tailwindcss(),
forbidMocksInProductionBundle(mode),
],
define: {
'__KOINKAT_ALLOW_MOCKS__': JSON.stringify(mocksAllowed),
'__KOINKAT_ALLOW_DEBUG_ROUTES__': JSON.stringify(debugRoutesAllowed),
'__KOINKAT_ALLOW_SANDBOX_UI__': JSON.stringify(sandboxUiAllowed),
},
clearScreen: false,
server: {
port: 1420,
strictPort: true,
host: host || false,
hmr: host ? { protocol: 'ws', host, port: 1421 } : undefined,
watch: { ignored: ['**/src-tauri/**'] },
},
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules/recharts') || id.includes('node_modules/date-fns')) {
return 'vendor-charts';
}
},
},
},
},
};
});
function forbidMocksInProductionBundle(mode: string): Plugin {
return {
name: 'koinkat-forbid-mocks-in-production',
enforce: 'post',
generateBundle(_opts, bundle) {
if (mode !== 'production') return;
for (const [name, chunk] of Object.entries(bundle)) {
if (chunk.type !== 'chunk') continue;
const leaked = (chunk.moduleIds ?? []).filter(id => id.includes('/src/mocks/'));
if (leaked.length > 0) {
throw new Error(
`Production bundle leaked mock code in chunk "${name}":\n ` +
leaked.join('\n ')
);
}
}
},
};
}
```
#### E. TypeScript globals - add to `src/vite-env.d.ts`
```ts
declare const __KOINKAT_ALLOW_MOCKS__: boolean;
declare const __KOINKAT_ALLOW_DEBUG_ROUTES__: boolean;
declare const __KOINKAT_ALLOW_SANDBOX_UI__: boolean;
```
#### F. Code changes to gate each surface
**`src/services/enable-banking-service.ts`** - guard the mock import so tree-shaking eliminates it in production:
```ts
import * as realService from './enable-banking-service-real';
const IS_MOCK = __KOINKAT_ALLOW_MOCKS__ && import.meta.env.VITE_EB_MOCK === 'true';
// Static import is guarded by the compile-time constant: when
// __KOINKAT_ALLOW_MOCKS__ is replaced with `false` by Vite define,
// the mock import becomes dead code and is eliminated by Rollup.
import * as mockService from '../mocks/mock-enable-banking-service';
export function listBanks(...) {
if (IS_MOCK) return mockService.listBanks(...);
return realService.listBanks(...);
}
// ... same pattern for every exported function
```
(In practice, the static import pattern relies on Rollup tree-shaking. If tree-shaking is insufficient, switch to a dynamic `await import('../mocks/...')` behind the `IS_MOCK` gate - the post-bundle scan catches any leak regardless.)
**`src/pages/BankLink.tsx`**:
- `const IS_MOCK_MODE = __KOINKAT_ALLOW_MOCKS__ && import.meta.env.VITE_EB_MOCK === 'true'`
- Wrap `MOCK_CODE_MAP` + mock branch of `handleConnect` in `if (__KOINKAT_ALLOW_MOCKS__ && IS_MOCK_MODE)`.
- Wrap InfoBanner in `{__KOINKAT_ALLOW_MOCKS__ && IS_MOCK_MODE && }`.
**`src/pages/Review.tsx`**: same pattern for the mock InfoBanner.
**`src/App.tsx`**:
```tsx
{__KOINKAT_ALLOW_DEBUG_ROUTES__ && (
} />
)}
```
Make the `Rules` import lazy: `const Rules = __KOINKAT_ALLOW_DEBUG_ROUTES__ ? lazy(() => import('./pages/Rules')) : null;`
**`src/pages/Connection.tsx`**:
- Wrap the Sandbox workspace-creation card in `{__KOINKAT_ALLOW_SANDBOX_UI__ && }`.
- Filter `ConnectionType` options list to exclude `'sandbox'` when `!__KOINKAT_ALLOW_SANDBOX_UI__`.
- Leave all sandbox runtime handling code intact (existing sandbox workspaces must continue to work). Only the "create new sandbox workspace" option is hidden.
### Why mocks cannot reach users in production - three layers of defense
1. **Compile-time flag replacement.** `__KOINKAT_ALLOW_MOCKS__` is replaced with the literal `false`. Rollup dead-code elimination removes every guarded branch and drops the now-unreferenced mock import.
2. **Build-time assertion.** `vite.config.ts` throws immediately if `VITE_EB_MOCK=true` is set when mode is `production`.
3. **Post-bundle scan.** The `forbidMocksInProductionBundle` plugin inspects every emitted chunk's `moduleIds` and fails the build if `/src/mocks/` appears anywhere - defense-in-depth for future contributors who might add new mock imports without a flag.
---
## 6. Risks & Open Questions
**Q1. `/rules` - fully remove from production or keep as a hidden admin tool?**
The Sidebar comment frames it as internal-only. But the Rules page IS functional (edits user/learned/MCC/system rules). If power users should be able to tweak categorization, consider surfacing it inside Settings rather than via a bare URL. If it's dev-only, remove in production as proposed.
**Proposed default:** fully removed in production, reachable in dev + demo.
**Q2. Sandbox workspace type - hide UI only, or also migrate schema?**
The proposal hides the Sandbox creation card in production. The DB schema keeps `'sandbox'` as valid, and existing sandbox workspaces keep working. Removing from the schema is a breaking migration - not recommended before v1.0.
**Proposed default:** hide UI only, schema intact.
**Q3. Demo build - separate app identifier or same as production?**
Separate (`com.koinkat.app.demo`) allows demo and production to coexist on the same machine. Same identifier means demo overwrites production. For tutorial recording, coexistence is safer.
**Proposed default:** separate identifier.
**Q4. Demo build - keep mock warning banners visible?**
The yellow InfoBanners in BankLink and Review confirm mock mode is active. For tutorial recordings they may be distracting. Options: (a) keep in demo - honest; (b) hide in demo, keep in dev only.
**Proposed default:** keep banners in demo.
**Q5. `.agents/`, `AGENTS.md`, `scripts/generate_mock.py` - delete or extract?**
For a solo project seeking feedback, these planning docs signal "unfinished". For an open-source project, architecture docs help contributors. The generation script could be legitimately useful if the mock fixtures need regenerating.
**Proposed default:** move useful architecture content into `docs/`; delete the rest.
**Q6. Remove unused direct deps `@tauri-apps/api` and `react-router`?**
Both confirmed unused. One-line `npm uninstall`. Low risk.
**Proposed default:** remove in the restructure.
**Q7. Bundle metadata for `tauri.conf.json` - in scope for this restructure?**
Production builds currently produce an unsigned, undescribed binary. This is a distribution blocker (no icon, no publisher, no signing) but separable from the mock/sandbox isolation work.
**Proposed default:** separate follow-up task.
**Q8. Were `KoinkatDemo/.env` and the `.pem` files ever committed?**
Run: `git log --all -- 'KoinkatDemo/.env' '47e53ca9*.pem' '957fc6d2*.pem'`
If any appear in git history, rotate the Enable Banking keys and Flask secrets, then decide between rewriting history or initializing a fresh repo (preferred for a brand-new public release - push a clean initial commit with no history).
**Action required before going public.**
**Q9. Deep-link scheme collision between demo and production builds.**
Both share `koinkat://` in `tauri.conf.json`. With separate identifiers the OS registers two handlers. If both are installed simultaneously the wrong instance may receive the OAuth callback. Worth testing during demo build validation.
**Proposed default:** note and test; likely fine in practice.
**Q10. CSP `connect-src` includes `https://cdn.jsdelivr.net` - is it used?**
No obvious call site was found. If unused, remove it from the CSP to reduce the allowed outbound surface.
**Action:** `grep -r 'jsdelivr' src/` - if zero results, remove from `tauri.conf.json`.
---
## Execution outcome (2026-04-24)
The restructure shipped. Deviations from the audit's proposal, all intentional:
1. **No `.env*` files exist.** The audit proposed four committed env files (`.env.development`, `.env.demo`, `.env.production`, `.env.example`) holding `VITE_EB_MOCK`. Instead, all `.env*` are gitignored and mock enablement is hardcoded in `vite.config.ts` via a new compile-time flag `__KOINKAT_EB_MOCK_DEFAULT__`. The runtime `VITE_EB_MOCK` env var is gone.
2. **Production mock defense is three layers, not two.** `__KOINKAT_ALLOW_MOCKS__` dead-codes mock branches; the compile-time replacement of `__KOINKAT_EB_MOCK_DEFAULT__` means `IS_MOCK` collapses to `false` in production; and `forbidMocksInProductionBundle` in `vite.config.ts` scans every chunk's `moduleIds` post-bundle and aborts if any `/src/mocks/` leaks through.
3. **Dev + demo both default mocks ON.** Contributors never need live Enable Banking credentials to run the app. Flip `mocksOnByDefault` in `vite.config.ts` to test the real client.
4. **`KoinkatDemo/`, the `.pem` files, `personal/`, and `.playwright-mcp/` were moved to `junk/` (gitignored) rather than deleted outright.** Nothing was ever committed to git history (verified via `git log --all --`). `junk/` is git-ignored, so public publishing is safe.
5. **`.claude/`, `.agents/`, and `AGENTS.md` are gitignored but kept on disk** for internal tooling. Per user preference.
6. **`app-icon.png` moved to `src-tauri/icons/source/app-icon.png`** rather than deleted - it's the source asset for the Tauri icon pipeline.
7. **`@tauri-apps/api` and `react-router` removed** from direct deps (unused). All bank/tauri plugins still satisfy their transitive needs.
Build verification on 2026-04-24: `npm run build` → 617 KB, `npm run build:demo` → 658 KB, `tsc --noEmit` clean. The 41 KB delta between builds is exactly the tree-shaken mock code.
Still open from the original audit:
- Q7 (tauri.conf.json bundle metadata - signing identity, icons, publisher, category) - blocker before public distribution.
- Q10 (prune `cdn.jsdelivr.net` from CSP) - **resolved as a non-issue, see Follow-up audit (2026-04-28) below.**
---
## Follow-up audit (2026-04-28)
A second read of the codebase after the restructure landed. Verifies what
the postscript above claimed and corrects one item.
### Q10 - CSP `cdn.jsdelivr.net` entry - KEEP
The original audit recommended pruning `https://cdn.jsdelivr.net` from the
CSP `connect-src` because no call site was found. That was wrong:
`src/lib/fx-fetch.ts:21-23` uses
`https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@…/v1/currencies/usd.json`
as the **primary** exchange-rate URL (with `*.currency-api.pages.dev` as
the fallback). Both hosts must remain in the CSP.
**Action:** none - leave the CSP entry alone.
### Confirmed in current state (2026-04-28)
- The four compile-time flags (`__KOINKAT_ALLOW_MOCKS__`,
`__KOINKAT_EB_MOCK_DEFAULT__`, `__KOINKAT_ALLOW_DEBUG_ROUTES__`,
`__KOINKAT_ALLOW_SANDBOX_UI__`) are wired exactly where the postscript
said they would be (`vite.config.ts`, `src/vite-env.d.ts`,
`enable-banking-service.ts:46`, `BankLink.tsx`, `Review.tsx`,
`App.tsx:41-43`, `Connection.tsx`).
- `package.json` scripts match: `dev`, `build` (mode=production),
`build:demo` (mode=demo), `preview`, `tauri`, `tauri:dev`, `tauri:build`,
`tauri:build:demo`, `typecheck`. Direct deps `@tauri-apps/api` and
`react-router` are gone.
- `src-tauri/tauri.conf.demo.json` exists and overrides only
`productName`, `identifier`, and `beforeBuildCommand`.
- `forbidMocksInProductionBundle` post-bundle plugin is in place at the
bottom of `vite.config.ts`.
- `.gitignore` covers `.env`, `.env.*`, `*.pem`, `*.key`, `*.p8`,
`*.p12`, `*.pfx`, `*.crt`, `*.cer`, `*.cert`, `*.der`, `*.asc`,
`*.gpg`, `*.jks`, `*.keystore`, `*.mobileprovision`, common SSH key
filenames, `credentials.json`, `secrets.json`, `service-account*.json`,
`.npmrc`, `.netrc`, `auth.json`, `secrets/`, `.secrets/`, `.tauri/`,
`junk/`, `KoinkatDemo/`, `.playwright-mcp/`, `personal/`, `scripts/`,
`.agents/`, `.claude/`, `AGENTS.md`.
- `junk/` is the on-disk archive - never tracked, never referenced from
source.
### Still open
| # | What | Urgency |
|---|------|---------|
| Q7 | `src-tauri/tauri.conf.json` `bundle.*` metadata. **Mostly resolved 2026-05-16** - icons (32 / 128 / 128@2x / .icns / .ico), `category: Finance`, short/long descriptions added (commit `1142713`); `copyright`, `publisher`, `homepage` filled (same-day follow-up to Marco Sburlino / `MarcoSburlino/Koinkat`). Still pending: no code-signing identity / notarization profile. | High - distribution blocker |
| - | No `README.md` at the repo root. **Resolved 2026-05-16** - minimal `README.md` added. | - |
| - | No `LICENSE` file. Deferred per maintainer choice until publication strategy decided. | Medium |
| - | No automated tests. **Partially resolved 2026-05-16** - `vitest@4.1.5` installed; `src/domain/money.test.ts` ships 42 tests (commit `1efb080`). Service / migration / UI tests not started. | Medium |
| - | `demo-service.ts` is misleadingly named - it is the **sandbox** workspace runtime, not anything to do with mocks or the demo build. Low-priority rename to `sandbox-service.ts`. | Low |
| - | `src/db/schema.sql` (legacy v1) is still on disk alongside `schema-v2.sql`. Kept for reference only - confirm nothing in the migration runner reads it. | Low |
| - | `src/components/ErrorBoundary.tsx` GitHub-issues link. **Resolved 2026-05-16** - points at `https://github.com/MarcoSburlino/Koinkat/issues/new`. | - |
| - | `src/lib/fx-fetch.ts` primary URL `cdn.jsdelivr.net` vs CSP. **Resolved 2026-05-16** - `cdn.jsdelivr.net` restored to `connect-src` (the "tightening" in `1142713` over-shot; the maintainer chose to keep jsDelivr as primary). | - |