feat: tasks app, security hardening, mobile fixes, iOS app shell

- Custom SQLite task manager replacing TickTick wrapper
- 73 tasks migrated from TickTick across 15 projects
- RRULE recurrence engine with lazy materialization
- Dashboard tasks widget (desktop sidebar + mobile card)
- Tasks page with project tabs, add/edit/complete/delete
- Security: locked ports to localhost, removed old containers
- Gitea Actions runner configured and all 3 CI jobs passing
- Fixed mobile overflow on dashboard cards
- iOS Capacitor app shell (Second Brain)
- Frontend/backend guide docs for adding new services
- TickTick Google Calendar sync re-authorized

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-03-30 15:35:57 -05:00
parent 877021ff20
commit 6023ebf9d0
49 changed files with 5207 additions and 23 deletions

View File

@@ -322,3 +322,146 @@ Shadows that are close to tokens but intentionally differ in blur radius or opac
| `0 20px 60px rgba(0,0,0,0.15)` | `--shadow-xl` | Single layer |
| `0 20px 60px rgba(0,0,0,0.2)` | `--shadow-xl` | Single layer, higher opacity |
| `0 1px 3px rgba(0,0,0,0.15)` | `--shadow-sm` | Toggle thumb, much higher opacity |
---
## Adding a New App (Frontend Guide)
Step-by-step for adding a new app page to the platform.
### 1. Create the route
Create `src/routes/(app)/yourapp/+page.svelte`. Every app is a single self-contained file.
### 2. Page structure template
```svelte
<script lang="ts">
import { onMount } from 'svelte';
// -- Types --
interface Item {
id: string;
name: string;
}
// -- State (Svelte 5 runes) --
let loading = $state(true);
let items = $state<Item[]>([]);
let activeTab = $state<'all' | 'recent'>('all');
let searchQuery = $state('');
// -- Derived --
const filtered = $derived(
items.filter(i => i.name.toLowerCase().includes(searchQuery.toLowerCase()))
);
// -- API helper (scoped to this app's gateway prefix) --
async function api(path: string, opts: RequestInit = {}) {
const res = await fetch(`/api/yourapp${path}`, { credentials: 'include', ...opts });
if (!res.ok) throw new Error(`${res.status}`);
return res.json();
}
// -- Data mapper (normalize backend shape to UI interface) --
function mapItem(raw: any): Item {
return { id: raw.id || raw.Id, name: raw.name || raw.title || '' };
}
// -- Load --
async function loadItems() {
try {
const data = await api('/items');
items = (data.items || data).map(mapItem);
} catch (e) { console.error('Failed to load items', e); }
}
onMount(async () => {
await loadItems();
loading = false;
});
</script>
<div class="page">
<div class="page-header">
<h1 class="page-title">Your App</h1>
</div>
{#if loading}
<div class="module"><div class="skeleton" style="height: 200px"></div></div>
{:else}
<div class="tab-bar">
<button class="tab" class:active={activeTab === 'all'} onclick={() => activeTab = 'all'}>All</button>
<button class="tab" class:active={activeTab === 'recent'} onclick={() => activeTab = 'recent'}>Recent</button>
</div>
<div class="module">
<div class="module-header">
<span class="module-title">Items</span>
<button class="module-action" onclick={...}>View all &rarr;</button>
</div>
{#each filtered as item}
<div class="data-row">
<span style="color: var(--text-1)">{item.name}</span>
</div>
{:else}
<p style="color: var(--text-3); padding: var(--card-pad)">No items found</p>
{/each}
</div>
{/if}
</div>
```
### 3. Register in navigation
**`(app)/+layout.server.ts`** -- add ID to allApps:
```ts
const allApps = ['trips', 'fitness', 'inventory', 'budget', 'reader', 'media', 'yourapp'];
```
**`Navbar.svelte`** -- add link:
```svelte
{#if showApp('yourapp')}
<a href="/yourapp" class="navbar-link" class:active={isActive('/yourapp')}>Your App</a>
{/if}
```
**`MobileTabBar.svelte`** -- add to primary tabs or the "More" sheet.
### 4. Key conventions
| Rule | Detail |
|------|--------|
| All state uses `$state()` | Never plain `let` for reactive values |
| Computed values use `$derived()` | For filtered lists, counts, conditions |
| API calls go through `/api/yourapp/*` | Gateway proxies to backend |
| `credentials: 'include'` on every fetch | Cookie-based auth |
| Map raw API data through `mapX()` | Normalize backend shapes to clean interfaces |
| No shared stores or context | Each page is self-contained |
| No separate `+page.ts` load function | Data loads in `onMount` |
| Types defined inline in script | No shared type files |
### 5. UI component classes to use
| Class | When |
|-------|------|
| `.page` / `.page-header` / `.page-title` | Page-level layout |
| `.module` / `.module.primary` / `.module.flush` | Card containers |
| `.module-header` / `.module-title` / `.module-action` | Card headers |
| `.data-row` | List items with hover + zebra |
| `.badge` + `.error/.success/.warning/.accent/.muted` | Status indicators |
| `.tab-bar` + `.tab` | Pill-style tabs |
| `.btn-primary` / `.btn-secondary` / `.btn-icon` | Buttons |
| `.input` | Text inputs |
| `.skeleton` | Loading placeholders |
| `.section-label` | Uppercase group header |
### 6. Styling rules
- All colors from tokens: `var(--text-1)`, `var(--accent)`, `var(--card)`, etc.
- All spacing from tokens: `var(--sp-3)`, `var(--card-pad)`, `var(--row-gap)`, etc.
- All radii from tokens: `var(--radius)`, `var(--radius-md)`, etc.
- All shadows from tokens: `var(--shadow-md)`, `var(--shadow-lg)`, etc.
- All font sizes from tokens: `var(--text-sm)`, `var(--text-base)`, etc.
- Never use raw `#hex` colors, raw `px` spacing, or raw shadows unless listed in the Intentional Raw Values section above
- Dark mode is automatic via CSS custom properties — no manual `prefers-color-scheme` needed