545 lines
20 KiB
Markdown
545 lines
20 KiB
Markdown
# Settings System — Kind 30078
|
|
|
|
This document is the definitive specification for how user settings and application-specific data are stored, encrypted, synced, and consumed across all projects in this ecosystem (client web pages, Didactyl agent, and future apps).
|
|
|
|
## Table of Contents
|
|
|
|
1. [Overview](#1-overview)
|
|
2. [NIP-78 Context](#2-nip-78-context)
|
|
3. [User-Centric Layout](#3-user-centric-layout)
|
|
4. [Centralized User Settings Event](#4-centralized-user-settings-event)
|
|
5. [Worker Lifecycle](#5-worker-lifecycle)
|
|
6. [Page-Level API](#6-page-level-api)
|
|
7. [Standalone Kind 30078 Events](#7-standalone-kind-30078-events)
|
|
8. [Cross-Project Alignment: Didactyl](#8-cross-project-alignment-didactyl)
|
|
9. [Encryption Standard](#9-encryption-standard)
|
|
10. [Adding a New Settings Namespace](#10-adding-a-new-settings-namespace)
|
|
11. [Audit Findings and Migration Plan](#11-audit-findings-and-migration-plan)
|
|
12. [Key Source Files](#12-key-source-files)
|
|
|
|
---
|
|
|
|
## 1. Overview
|
|
|
|
The app uses **NIP-78 Application-specific Data** (`kind:30078`) for two distinct purposes:
|
|
|
|
1. **Centralized User Settings** — A single addressable event (`d:user-settings`) containing a namespaced JSON object. This is the user's portable preference file, designed to work across apps.
|
|
2. **Standalone Page Data** — Individual addressable events with page-specific `d` tags for large or specialized data stores.
|
|
|
|
Both are **parameterized replaceable events**: for a given `pubkey + kind + d-tag`, only the latest event is retained by relays.
|
|
|
|
```mermaid
|
|
graph TD
|
|
subgraph Centralized - d:user-settings
|
|
W[SharedWorker ndk-worker.js]
|
|
W -->|hydrate on init| C[IndexedDB cache]
|
|
W -->|fetch + decrypt| R[Relays]
|
|
W -->|NIP-44 encrypt + publish| R
|
|
P1[Page A] -->|getUserSettings| W
|
|
P2[Page B] -->|patchUserSettings| W
|
|
P3[Page C] -->|onUserSettings| W
|
|
D[Didactyl Agent] -->|reads llm from user prefs| R
|
|
end
|
|
|
|
subgraph Standalone - per-page d-tags
|
|
S1[todo.html] -->|d:todo| R2[Relays]
|
|
S2[cal.html] -->|d:calorie_foods / d:calorie_diary| R2
|
|
S3[links.html] -->|d:links| R2
|
|
S4[ai.html] -->|d:convo-id with t:client-ai-chat-v1| R2
|
|
S5[keep-alive.html] -->|d:relay-list-N with t:relay-list| R2
|
|
S6[post.html] -->|d:viewed| R2
|
|
end
|
|
```
|
|
|
|
---
|
|
|
|
## 2. NIP-78 Context
|
|
|
|
[NIP-78](../reference_repos/nips/78.md) specifies kind `30078` as an addressable event with a `d` tag containing "some reference to the app name and context — or any other arbitrary string." Content and tags can be anything.
|
|
|
|
**NIP-78 is intentionally a blank canvas.** No NIP defines:
|
|
- A standard schema for user preferences
|
|
- A convention for d-tag naming
|
|
- A cross-app settings format
|
|
|
|
This means our `d:user-settings` convention is ours to define. If it proves useful across the Nostr ecosystem, it could eventually become a NIP proposal for standardized user preferences.
|
|
|
|
**Related NIPs:**
|
|
- **NIP-44** — The required encryption standard for settings content (NIP-04 is deprecated)
|
|
- **NIP-51** — Defines structured lists (kinds 10000-30007) but not app settings
|
|
- **NIP-37** — Defines draft wraps (kind 31234) with separate relay lists for private content
|
|
|
|
---
|
|
|
|
## 3. User-Centric Layout
|
|
|
|
From the user's npub perspective, their kind 30078 events should be organized into three categories:
|
|
|
|
### Category 1: Cross-App User Preferences (`d:user-settings`)
|
|
|
|
Settings that any compatible app should respect. These follow the user across devices and apps:
|
|
|
|
| Namespace | Purpose | Example consumers |
|
|
|-----------|---------|-------------------|
|
|
| `global_llm` | LLM provider, model, API key, multi-provider config | AI pages, skills-edit, Didactyl agent |
|
|
| `global_zaps` | Default zap amount, comment, preferred method, mint allowlist | Cashu page, post interactions, any zap button |
|
|
| `global_ui` | Theme, language, accessibility preferences | All pages |
|
|
| `global_relays` | Relay preferences | All pages |
|
|
| `global_experimental` | Feature flags | All pages |
|
|
|
|
The `global_` prefix distinguishes cross-app settings from page-specific ones at a glance.
|
|
|
|
### Category 2: Page-Specific Settings (also in `d:user-settings`)
|
|
|
|
Layout and UI state that only matters to specific pages within client:
|
|
|
|
| Namespace | Purpose | Owning page |
|
|
|-----------|---------|-------------|
|
|
| `feed` | Video autoplay | `feed.html` |
|
|
| `post` | Viewed scroll behavior | `post.html` |
|
|
| `notifications` | Filters, readAt timestamp | `notifications.html` |
|
|
| `blobs` | Grid columns, rows per page | `blobs.html` |
|
|
| `vjPage` | Streaming sites, columns, draft | `vj.html` |
|
|
| `strudel` | Strudel music coding settings | strudel pages |
|
|
|
|
No prefix — these are clearly page-local by their names.
|
|
|
|
### Category 3: Standalone Data Events (separate d-tags)
|
|
|
|
Large data stores that would bloat the centralized settings event:
|
|
|
|
| d-tag | Purpose | Why standalone |
|
|
|-------|---------|---------------|
|
|
| `todo` | Todo list items | Can grow large |
|
|
| `calorie_foods` / `calorie_diary` | Calorie tracking data | Separate data domains |
|
|
| `links` | Bookmarks (Netscape HTML) | Large, compressed |
|
|
| `viewed` | Read/unread tracking per follow | Updates frequently |
|
|
| `{conversation-id}` | AI chat conversations | Many events, large |
|
|
| `relay-list-{N}` | Relay registry chunks | Intentionally public |
|
|
| `show-playlist:{show}:{ts}` | VJ episode playlists | Intentionally public |
|
|
|
|
---
|
|
|
|
## 4. Centralized User Settings Event
|
|
|
|
### Nostr Event Shape
|
|
|
|
```json
|
|
{
|
|
"kind": 30078,
|
|
"tags": [["d", "user-settings"]],
|
|
"content": "<NIP-44 encrypted JSON>",
|
|
"created_at": 1708646400
|
|
}
|
|
```
|
|
|
|
- **`d` tag**: Always `"user-settings"`
|
|
- **Encryption**: NIP-44 self-encrypt (sender = recipient = user pubkey)
|
|
- **Replaceability**: Addressable — publishing a new one replaces the previous on relays
|
|
|
|
### Target Schema (v2)
|
|
|
|
```json
|
|
{
|
|
"v": 2,
|
|
"updatedAt": 1708646400,
|
|
|
|
"global_llm": {
|
|
"provider": "ppq",
|
|
"api_key": "sk-...",
|
|
"model": "claude-opus-4.6",
|
|
"base_url": "https://api.ppq.ai",
|
|
"max_tokens": 200000,
|
|
"temperature": 0.7,
|
|
"providers": [
|
|
{
|
|
"name": "ppq",
|
|
"base_url": "https://api.ppq.ai",
|
|
"api_key": "sk-...",
|
|
"models": ["claude-opus-4.6", "claude-haiku-4.5"]
|
|
}
|
|
],
|
|
"favorites": ["claude-opus-4.6"]
|
|
},
|
|
|
|
"global_zaps": {
|
|
"defaultAmountSats": 21,
|
|
"defaultComment": "",
|
|
"preferredMethod": "auto",
|
|
"receiveMintAllowlist": []
|
|
},
|
|
|
|
"global_ui": {},
|
|
"global_relays": {},
|
|
"global_experimental": {},
|
|
|
|
"feed": { "videoAutoplay": false },
|
|
"post": { "viewed": { "scrollToMark": false } },
|
|
"notifications": { "filters": {}, "readAt": 0 },
|
|
"blobs": { "gridColumns": 4, "rowsPerPage": 10 },
|
|
"vjPage": {
|
|
"streamingSites": [],
|
|
"selectedSiteName": "",
|
|
"streamDraft": {},
|
|
"autoAnnounce": {},
|
|
"columns": {}
|
|
},
|
|
"strudel": {}
|
|
}
|
|
```
|
|
|
|
### Current Schema (v1) — Supported
|
|
|
|
The current implementation uses these namespace names without the `global_` prefix:
|
|
|
|
| Current key | Target key | Status |
|
|
|-------------|------------|--------|
|
|
| `ui` | `global_ui` | Rename pending |
|
|
| `relays` | `global_relays` | Rename pending |
|
|
| `experimental` | `global_experimental` | Rename pending |
|
|
| `zaps` | `global_zaps` | Rename pending |
|
|
| `ai` | `global_llm` | Rename + schema alignment pending |
|
|
| `post` | `post` | No change |
|
|
| `feed` | `feed` | No change |
|
|
| `notifications` | `notifications` | No change |
|
|
| `blobs` | `blobs` | No change |
|
|
| `vjPage` | `vjPage` | No change |
|
|
| `strudel` | `strudel` | No change |
|
|
|
|
### Default Settings
|
|
|
|
Defined in [`getDefaultUserSettings()`](../www/ndk-worker.js:324):
|
|
|
|
```js
|
|
{
|
|
v: 1, // SETTINGS_SCHEMA_VERSION
|
|
updatedAt: 0,
|
|
ui: {},
|
|
post: { viewed: { scrollToMark: false } },
|
|
strudel: {},
|
|
relays: {},
|
|
experimental: {}
|
|
}
|
|
```
|
|
|
|
Feature namespaces not in the defaults (e.g. `feed`, `notifications`, `zaps`, `blobs`, `ai`, `vjPage`) are created on first patch. The [`deepMerge()`](../www/ndk-worker.js:344) function ensures new keys are added without destroying existing ones.
|
|
|
|
### Normalization
|
|
|
|
[`normalizeUserSettings()`](../www/ndk-worker.js:359) deep-merges any input with the defaults, then forces:
|
|
- `v` to the current `SETTINGS_SCHEMA_VERSION`
|
|
- `updatedAt` to a numeric value
|
|
|
|
---
|
|
|
|
## 5. Worker Lifecycle
|
|
|
|
### Hydration (startup)
|
|
|
|
Called by [`hydrateUserSettingsForPubkey()`](../www/ndk-worker.js:3992) during [`handleInit()`](../www/ndk-worker.js:4271):
|
|
|
|
```
|
|
1. Read from IndexedDB cache (ndk-shared-settings DB, kv store)
|
|
2. Normalize and broadcast to all connected tabs
|
|
3. Fetch kind 30078 d:user-settings from relays
|
|
4. NIP-44 decrypt the content
|
|
5. Compare updatedAt timestamps — relay wins if >= local
|
|
6. Write winner to IndexedDB cache
|
|
7. Broadcast final settings to all tabs
|
|
```
|
|
|
|
### Publishing
|
|
|
|
Called by [`publishUserSettingsNow()`](../www/ndk-worker.js:4018):
|
|
|
|
```
|
|
1. Set updatedAt to current unix timestamp
|
|
2. Normalize the settings object
|
|
3. JSON.stringify the normalized object
|
|
4. NIP-44 encrypt (self-encrypt: sender = recipient = user pubkey)
|
|
5. Create NDKEvent with kind:30078, tags:[['d','user-settings']]
|
|
6. Sign and publish to connected relays
|
|
```
|
|
|
|
### Debounced Publish
|
|
|
|
[`scheduleUserSettingsPublish()`](../www/ndk-worker.js:4055) debounces rapid patches with a 250-350ms delay so multiple quick UI changes result in a single relay publish.
|
|
|
|
---
|
|
|
|
## 6. Page-Level API
|
|
|
|
All functions are exported from [`www/js/init-ndk.mjs`](../www/js/init-ndk.mjs) and communicate with the SharedWorker via `postMessage`.
|
|
|
|
### `getUserSettings()` -> `Promise<Object>`
|
|
|
|
[Source](../www/js/init-ndk.mjs:956). Returns the current merged settings object. Times out after 7 seconds.
|
|
|
|
### `patchUserSettings(patch, options?)` -> `Promise<Object>`
|
|
|
|
[Source](../www/js/init-ndk.mjs:985). Deep-merges `patch` into current settings, writes to cache, broadcasts to all tabs, and (unless `options.publish === false`) schedules a debounced relay publish.
|
|
|
|
**Convention**: Each page patches only its own namespace:
|
|
|
|
```js
|
|
await patchUserSettings({
|
|
myFeature: { someSetting: true }
|
|
});
|
|
```
|
|
|
|
### `onUserSettings(callback)` -> `unsubscribe function`
|
|
|
|
[Source](../www/js/init-ndk.mjs:1015). Subscribes to live settings updates via the `ndkUserSettings` custom DOM event. Returns an unsubscribe function.
|
|
|
|
### Standard Page Pattern
|
|
|
|
```js
|
|
// 1. Import
|
|
import { getUserSettings, patchUserSettings, onUserSettings } from './js/init-ndk.mjs';
|
|
|
|
// 2. Initial read
|
|
let pageSettings = {};
|
|
try {
|
|
pageSettings = await getUserSettings();
|
|
} catch (error) {
|
|
pageSettings = {};
|
|
}
|
|
|
|
// 3. Subscribe to live updates
|
|
const unsubscribe = onUserSettings((settings) => {
|
|
pageSettings = settings || {};
|
|
applySettings(pageSettings);
|
|
});
|
|
|
|
// 4. Patch your namespace
|
|
await patchUserSettings({ myFeature: { key: value } });
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Standalone Kind 30078 Events
|
|
|
|
These are **not** part of the centralized user-settings object. Each page manages its own `d`-tagged event independently.
|
|
|
|
| d-tag | Page | Encryption | Content Format | Description |
|
|
|-------|------|------------|----------------|-------------|
|
|
| `todo` | [`todo.html`](../www/todo.html:831) | NIP-04 (legacy) | `{ rows: [todo items] }` | User todo list |
|
|
| `calorie_foods` | [`cal.html`](../www/cal.html:975) | NIP-04 (legacy) | `{ rows: [food items] }` | Calorie food database |
|
|
| `calorie_diary` | [`cal.html`](../www/cal.html:975) | NIP-04 (legacy) | `{ rows: [diary entries] }` | Daily food diary |
|
|
| `links` | [`links.html`](../www/links.html:497) | NIP-04 + LZW (legacy) | Netscape bookmark HTML | Saved bookmarks |
|
|
| `viewed` | [`post.html`](../www/post.html) | NIP-44 | `{ v:1, lastGlobalView, follows: {} }` | Read/unread tracking |
|
|
| `relay-list-{N}` | [`keep-alive.html`](../www/keep-alive.html:1162) | Plaintext | JSON relay metadata chunks | Relay registry |
|
|
| `{conversation-id}` | [`ai.html`](../www/ai.html:788) | NIP-44 | `{ id, title, messages, ... }` | AI chat conversations |
|
|
| `show-playlist:{show}:{ts}` | [`vj-stream.mjs`](../www/js/vj-stream.mjs:5) | Plaintext | Episode playlist with track tags | VJ episode playlists |
|
|
|
|
### Why Standalone?
|
|
|
|
- **Size**: Todo lists, bookmarks, and conversations can be large
|
|
- **Update frequency**: Conversations and playlists update frequently and independently
|
|
- **Different encryption**: Some use NIP-04 (legacy), some NIP-44, some are plaintext
|
|
- **Different audiences**: Relay registry and playlists are intentionally public
|
|
|
|
---
|
|
|
|
## 8. Cross-Project Alignment: Didactyl
|
|
|
|
### Didactyl's Kind 30078 Usage
|
|
|
|
The Didactyl agent (a C binary with its own Nostr identity) uses kind 30078 with NIP-44 self-encrypted payloads:
|
|
|
|
| d-tag | Source | Content |
|
|
|-------|--------|---------|
|
|
| `user-settings` | [`main.c`](/home/user/lt/didactyl/src/main.c) | `{ v, updatedAt, global_llm, didactyl }` |
|
|
| `tasks` | [`tool_task.c:14`](/home/user/lt/didactyl/src/tools/tool_task.c:14) | Agent task memory |
|
|
| `memory` | [`tool_memory.c:14`](/home/user/lt/didactyl/src/tools/tool_memory.c:14) | Agent long-term memory |
|
|
| Any d-tag | [`tool_config.c:128`](/home/user/lt/didactyl/src/tools/tool_config.c:128) | Generic config_store/config_recall |
|
|
|
|
### Didactyl `user-settings` Shape
|
|
|
|
Didactyl now stores runtime LLM + agent metadata in a single `d:user-settings` event under the **agent's own pubkey**:
|
|
|
|
```json
|
|
{
|
|
"v": 2,
|
|
"updatedAt": 1712345678,
|
|
"global_llm": {
|
|
"provider": "ppq",
|
|
"api_key": "sk-...",
|
|
"model": "claude-opus-4.6",
|
|
"base_url": "https://api.ppq.ai",
|
|
"max_tokens": 200000,
|
|
"temperature": 0.7
|
|
},
|
|
"didactyl": {
|
|
"admin_pubkey": "npub1...",
|
|
"dm_protocol": "nip04"
|
|
}
|
|
}
|
|
```
|
|
|
|
`model_set` performs read-modify-write of this event by patching `global_llm` while preserving other namespaces.
|
|
|
|
### Cross-Project Reading
|
|
|
|
Didactyl does **not** read admin/user web `user-settings` for runtime startup. It only reads/writes:
|
|
|
|
```
|
|
kind:30078, authors:[agent_pubkey], #d:[user-settings]
|
|
```
|
|
|
|
If a web page wants to inspect agent runtime settings, it should query the agent pubkey's `d:user-settings` event and read:
|
|
|
|
- `global_llm` for model/provider/api settings
|
|
- `didactyl` for agent-specific runtime metadata
|
|
|
|
---
|
|
|
|
## 9. Encryption Standard
|
|
|
|
| Context | Method | Rationale |
|
|
|---------|--------|-----------|
|
|
| Centralized user-settings | NIP-44 self-encrypt | Modern standard; worker handles via messageSigner |
|
|
| `viewed` (post read state) | NIP-44 self-encrypt | Privacy — relays cannot see read state |
|
|
| AI conversations | NIP-44 self-encrypt | Contains private chat history |
|
|
| Didactyl configs | NIP-44 self-encrypt | Contains API keys |
|
|
| `todo` | **NIP-04 (legacy — migrate)** | Predates NIP-44 |
|
|
| `calorie_foods` / `calorie_diary` | **NIP-04 (legacy — migrate)** | Predates NIP-44 |
|
|
| `links` | **NIP-04 + LZW (legacy — migrate)** | Predates NIP-44 |
|
|
| `relay-list-{N}` | Plaintext | Intentionally public |
|
|
| VJ episode playlists | Plaintext | Intentionally public |
|
|
|
|
**NIP-04 is deprecated.** All legacy pages should migrate to NIP-44.
|
|
|
|
---
|
|
|
|
## 10. Adding a New Settings Namespace
|
|
|
|
### Step 1: Choose centralized vs standalone
|
|
|
|
- Small config that benefits from cross-tab sync -> centralized namespace
|
|
- Large data, high-frequency updates, or different encryption needs -> standalone d-tag
|
|
- Cross-app portable preference -> centralized with `global_` prefix
|
|
|
|
### Step 2: For centralized (recommended for most page settings)
|
|
|
|
```js
|
|
import { getUserSettings, patchUserSettings, onUserSettings } from './js/init-ndk.mjs';
|
|
|
|
// Read on init
|
|
const settings = await getUserSettings();
|
|
const myConfig = settings?.myNewFeature || {};
|
|
|
|
// Subscribe to live updates
|
|
onUserSettings((s) => {
|
|
const updated = s?.myNewFeature || {};
|
|
applyMyConfig(updated);
|
|
});
|
|
|
|
// Save changes
|
|
await patchUserSettings({
|
|
myNewFeature: { option1: true, option2: 'value' }
|
|
});
|
|
```
|
|
|
|
### Step 3: For standalone
|
|
|
|
```js
|
|
import { subscribe, publishEvent } from './js/init-ndk.mjs';
|
|
|
|
// Read
|
|
subscribe(
|
|
{ kinds: [30078], authors: [pubkey], '#d': ['my-feature-data'] },
|
|
{ closeOnEose: true, cacheUsage: 'CACHE_FIRST' }
|
|
);
|
|
|
|
// Write (use NIP-44 encryption via worker messageSigner)
|
|
await publishEvent({
|
|
kind: 30078,
|
|
tags: [['d', 'my-feature-data']],
|
|
content: encryptedContent,
|
|
created_at: Math.floor(Date.now() / 1000)
|
|
});
|
|
```
|
|
|
|
### Step 4: Document
|
|
|
|
Add the new namespace to the appropriate table in this file.
|
|
|
|
---
|
|
|
|
## 11. Audit Findings and Migration Plan
|
|
|
|
### Issue 1: Duplicate AI Config Storage — RESOLVED
|
|
|
|
**Previous problem**: AI provider config was stored in two places:
|
|
- `skills-edit.html` used standalone user-pubkey `d:llm_config`
|
|
- Other AI pages used centralized user settings (`settings.ai` / `global_llm`) via `patchUserSettings`
|
|
|
|
**Resolution implemented**:
|
|
- `skills-edit.html` now reads from centralized user settings `global_llm` (with `ai` fallback for v1 compatibility)
|
|
- `skills-edit.html` now writes LLM updates via `patchUserSettings({ global_llm: ... })`
|
|
- Standalone user-pubkey `d:llm_config` is deprecated
|
|
- Agent-pubkey standalone `d:llm_config` remains the Didactyl runtime config format
|
|
|
|
### Issue 2: NIP-04 Legacy Encryption — HIGH PRIORITY
|
|
|
|
**Problem**: Three pages use deprecated NIP-04 encryption:
|
|
- [`todo.html`](../www/todo.html:822) — `window.nostr.nip04.encrypt()`
|
|
- [`cal.html`](../www/cal.html:972) — `window.nostr.nip04.encrypt()`
|
|
- [`links.html`](../www/links.html:501) — `window.nostr.nip04.encrypt()` + LZW
|
|
|
|
**Migration**: Switch to NIP-44. Add one-time migration: read NIP-04, re-encrypt with NIP-44, republish.
|
|
|
|
### Issue 3: Direct `window.nostr` Calls — MEDIUM PRIORITY
|
|
|
|
**Problem**: Standalone pages call `window.nostr.nip04.encrypt/decrypt` directly, bypassing the worker's `messageSigner` infrastructure. This breaks with remote signers/bunkers.
|
|
|
|
**Migration**: Route encryption through the worker's `messageSigner` for consistency.
|
|
|
|
### Issue 4: Inconsistent Filter Syntax — LOW PRIORITY
|
|
|
|
**Problem**: [`cal.html`](../www/cal.html:997) passes filter as an array `[{ kinds: [30078], ... }]` instead of a plain object.
|
|
|
|
**Migration**: Normalize to object form.
|
|
|
|
### Issue 5: Missing `cacheUsage: 'CACHE_FIRST'` — LOW PRIORITY
|
|
|
|
**Problem**: [`todo.html`](../www/todo.html:863), [`cal.html`](../www/cal.html:998), [`links.html`](../www/links.html:434) subscribe without `cacheUsage: 'CACHE_FIRST'`.
|
|
|
|
**Migration**: Add `cacheUsage: 'CACHE_FIRST'` for faster load times.
|
|
|
|
### Issue 6: Merge `calorie_foods` + `calorie_diary` — LOW PRIORITY
|
|
|
|
**Problem**: [`cal.html`](../www/cal.html:1103) publishes two separate events that are always loaded and saved together.
|
|
|
|
**Migration**: Combine into single `d:calorie` event: `{ foods: {...}, diary: {...} }`.
|
|
|
|
### Issue 7: Rename to `global_` Prefix — DEFERRED
|
|
|
|
**Problem**: Current cross-app namespaces (`ai`, `zaps`, `ui`, `relays`, `experimental`) lack the `global_` prefix.
|
|
|
|
**Migration**: Bump schema to v2. In `normalizeUserSettings()`, detect v1 and migrate: copy `ai` -> `global_llm`, `zaps` -> `global_zaps`, etc. Support reading both during transition.
|
|
|
|
### Migration Priority Order
|
|
|
|
1. Merge `skills-edit.html` `d:llm_config` into centralized settings (Issue 1)
|
|
2. Migrate `todo.html`, `cal.html`, `links.html` from NIP-04 to NIP-44 (Issue 2)
|
|
3. Route standalone encryption through worker messageSigner (Issue 3)
|
|
4. Fix `cal.html` filter syntax + add CACHE_FIRST everywhere (Issues 4, 5)
|
|
5. Merge calorie events (Issue 6)
|
|
6. Rename to `global_` prefix with v2 schema migration (Issue 7)
|
|
|
|
---
|
|
|
|
## 12. Key Source Files
|
|
|
|
| File | Role |
|
|
|------|------|
|
|
| [`www/ndk-worker.js`](../www/ndk-worker.js:311) | Settings state, hydration, publish, cache, normalize |
|
|
| [`www/js/init-ndk.mjs`](../www/js/init-ndk.mjs:956) | Page-facing API: getUserSettings, patchUserSettings, onUserSettings |
|
|
| [`reference_repos/nips/78.md`](../reference_repos/nips/78.md) | NIP-78 specification for kind 30078 |
|
|
| [`reference_repos/nips/44.md`](../reference_repos/nips/44.md) | NIP-44 encryption specification |
|
|
| `/home/user/lt/didactyl/src/main.c` | Didactyl agent llm_config and agent_config publish/recall |
|
|
| `/home/user/lt/didactyl/src/tools/tool_config.c` | Didactyl generic config_store/config_recall tool |
|
|
| `/home/user/lt/didactyl/src/tools/tool_model.c` | Didactyl model_set tool (persists to d:llm_config) |
|