# FailureNTS™ / Note to self Architecture

Version: `0.0.46`  
Home Assistant domain: `note_to_self`  
Panel path: `/note-to-self`

FailureNTS™ is a local Home Assistant custom integration and custom panel for private notes, shared notes, shared category threads, attachments, reminders, self-destructive timers, user preferences, and ZIP import/export.

The integration is intentionally Home-Assistant-native:

- the logged-in Home Assistant user is the trust boundary;
- the frontend cannot choose note ownership;
- metadata is stored with Home Assistant's `.storage` helper;
- uploaded files are stored on disk under the Home Assistant config folder;
- attachment/download/import/export URLs are short-lived opaque HTTP tokens prepared over authenticated WebSocket calls.

---

## 1. High-level layout

```text
/config
├─ custom_components/
│  └─ note_to_self/
│     ├─ __init__.py          # Integration setup, panel registration, services
│     ├─ config_flow.py       # Single-entry UI config flow
│     ├─ const.py             # Domain constants, limits, version, paths
│     ├─ http.py              # Tokenized HTTP views for files/import/export
│     ├─ manager.py           # Core business logic and permission checks
│     ├─ models.py            # Dataclasses for notes, attachments, shares
│     ├─ services.yaml        # Service action descriptions
│     ├─ storage.py           # Home Assistant Store wrapper
│     ├─ websocket_api.py     # WebSocket command adapter layer
│     └─ translations/
│        └─ en.json
│
├─ www/
│  └─ note-to-self/
│     ├─ note-to-self-panel.js             # Bundled runtime panel, v0.0.46
│     ├─ note-to-self-panel-app.js         # Source-style panel app module
│     ├─ note-to-self-panel-shell.js       # Panel shell template
│     ├─ note-to-self-panel-renderers.js   # Feed/tree render helpers
│     ├─ note-to-self-panel-utils.js       # Formatting, badge and helper utilities
│     └─ note-to-self-api.js               # Frontend API wrapper
│
├─ .storage/
│  └─ note_to_self.storage    # Metadata store, managed by HA Store helper
│
└─ note_to_self_uploads/
   └─ <owner_user_id>/
      └─ YYYY/MM/             # Attachment file storage
```

---

## 2. Runtime startup flow

```mermaid
flowchart TD
    A[Home Assistant starts] --> B[async_setup]
    B --> C[Register WebSocket commands]
    B --> D[Register HTTP token views]
    B --> E[Register service actions]
    F[Config entry setup] --> G[Create NoteToSelfStorage]
    G --> H[Create NoteToSelfManager]
    H --> I[Load .storage/note_to_self.storage]
    I --> J[Register custom panel]
    J --> K[/note-to-self loads frontend module]
```

`__init__.py` is the integration entrypoint. It registers:

- WebSocket commands from `websocket_api.py`;
- HTTP routes from `http.py`;
- service actions from `services.yaml` / service handlers;
- the Home Assistant sidebar panel with component name `note-to-self-panel`.

The panel module URL is versioned in `const.py`:

```text
/local/note-to-self/note-to-self-panel.js?v=0.0.46
```

That query-string version is used to force browsers to fetch the corrected frontend bundle after an upgrade.

---

## 3. Backend modules

### `const.py`

Holds the stable domain constants and safety limits:

- domain: `note_to_self`;
- panel version: `0.0.46`;
- storage key: `note_to_self.storage`;
- upload directory name: `note_to_self_uploads`;
- attachment size/count limits;
- blocked executable/script attachment extensions and MIME types;
- token TTL values for download, upload, export, attachment archive and import flows.

### `storage.py`

Wraps Home Assistant's `Store` helper.

Persisted metadata shape:

```json
{
  "version": 1,
  "users": {}
}
```

The manager owns the detailed per-user contents under `users`.

### `models.py`

Defines the persisted data model:

- `AttachmentRecord`
- `ShareConfig`
- `NoteRecord`

A note contains:

```text
id
owner_user_id
thread_owner_user_id
title
content
category
tags
attachments
created_at
updated_at
pinned
archived
draft
self_destruct_at
reminder_disabled_key
share
```

`thread_owner_user_id` is important for shared-category chat behavior. A recipient can post a new note into another user's shared category while the message itself remains owned by the posting user.

### `manager.py`

This is the core domain layer. It owns:

- storage loading and saving;
- note CRUD;
- category and note sharing;
- permission checks;
- category thread behavior;
- user preferences;
- mobile notification target discovery;
- attachment validation and storage;
- token creation and expiry;
- ZIP export/import and duplicate preview;
- draft and self-destruct cleanup.

The manager uses one async lock around mutable storage state so concurrent WebSocket/HTTP calls do not corrupt the in-memory representation.

### `websocket_api.py`

Authenticated WebSocket command adapter. It reads the active Home Assistant user from the WebSocket connection and forwards validated payloads to the manager.

Main command groups:

```text
note_to_self/list_feed
note_to_self/list_notes
note_to_self/get_note

note_to_self/create_note
note_to_self/update_note
note_to_self/delete_note

note_to_self/list_share_targets
note_to_self/get_note_share
note_to_self/save_note_share
note_to_self/delete_note_share
note_to_self/get_category_share
note_to_self/save_category_share
note_to_self/delete_category_share
note_to_self/apply_share_mode_changes

note_to_self/get_preferences
note_to_self/save_preferences
note_to_self/list_mobile_notification_targets

note_to_self/prepare_attachment_upload
note_to_self/upload_attachment
note_to_self/delete_attachment
note_to_self/get_attachment_url
note_to_self/prepare_note_attachment_archive

note_to_self/prepare_export
note_to_self/prepare_import
note_to_self/confirm_import
```

### `http.py`

Tokenized unauthenticated HTTP views. These routes do not directly trust the browser. They only work after an authenticated WebSocket command has created a short-lived token.

Routes:

```text
GET  /api/note_to_self/attachment/{token}
POST /api/note_to_self/upload/{token}
GET  /api/note_to_self/export/{token}
GET  /api/note_to_self/attachment_archive/{token}
POST /api/note_to_self/import/{token}
```

The token points back to a prepared manager-side record containing the owner, note, attachment or import/export context.

---

## 4. Frontend modules

The deployed Home Assistant panel loads:

```text
www/note-to-self/note-to-self-panel.js
```

That file is the bundled runtime file used by Home Assistant. The adjacent module files keep the codebase readable and explain the intended split:

- `note-to-self-api.js` wraps Home Assistant WebSocket calls and tokenized HTTP upload/download calls.
- `note-to-self-panel-app.js` contains panel state and high-level UI behavior.
- `note-to-self-panel-shell.js` builds the static shadow DOM shell.
- `note-to-self-panel-renderers.js` renders the note tree, feed, modals and pending attachments.
- `note-to-self-panel-utils.js` contains formatting, category/tag color parsing and helper functions.

The panel is a custom element:

```text
<note-to-self-panel>
```

The UI model is messenger-style:

```text
left tree/search/categories/tags  |  message feed
                                  |  sticky composer
```

Important frontend state includes:

- active category;
- query text;
- selected notes/categories in delete/share modes;
- pending attachment queue;
- staged attachment removals while editing;
- import preview state;
- user preference toggles;
- share modal state;
- live refresh timer state.

---

## 5. Note visibility and permissions

The backend always decides visibility. The frontend only displays returned permissions.

### Owner

The owner can:

- read the note;
- edit all fields;
- change category;
- pin or archive;
- add and remove attachments;
- manage note share settings;
- manage category share settings;
- hard delete the note.

### Selected-user or public recipient

A recipient can read a visible shared note and collaborate on message content-level fields:

- title;
- content;
- tags;
- attachments.

A recipient cannot change owner-only fields:

- category;
- pin/archive state;
- share settings;
- hard delete.

### Shared category thread

Category sharing acts like a live rule:

- current notes in the category become visible according to the rule;
- future notes in that category inherit the category visibility;
- recipients can post into the shared thread;
- the posted message remains owned by the posting user;
- the shared thread still points back to the category owner's thread via `thread_owner_user_id`.

### Received-share hiding

Recipients can hide received shared categories or received note shares from their own view. That does not delete the owner's data. A future re-share can make the content visible again if the share revision changes.

---

## 6. Storage model

Metadata is stored in:

```text
/config/.storage/note_to_self.storage
```

Attachments are stored separately in:

```text
/config/note_to_self_uploads/<owner_user_id>/YYYY/MM/
```

This separation keeps Home Assistant's JSON storage from becoming a large binary blob.

Typical per-user bucket responsibilities:

```text
users
└─ <user_id>
   ├─ notes
   ├─ category_shares
   ├─ blocked_received_category_shares
   ├─ blocked_received_note_shares
   └─ preferences
```

Attachment metadata lives inside each note, while the file bytes live on disk.

---

## 7. Attachment flow

```mermaid
sequenceDiagram
    participant UI as Frontend panel
    participant WS as HA WebSocket
    participant M as NoteToSelfManager
    participant HTTP as Tokenized HTTP view
    participant FS as File system

    UI->>WS: prepare_attachment_upload(note_id, filename, mime)
    WS->>M: validate user, note and attachment rules
    M-->>WS: short-lived upload URL
    WS-->>UI: upload URL
    UI->>HTTP: POST file bytes to /api/note_to_self/upload/{token}
    HTTP->>M: resolve upload token
    M->>FS: save bytes under note_to_self_uploads
    M->>M: append AttachmentRecord to note
    M-->>HTTP: attachment metadata
    HTTP-->>UI: upload result
```

Validation includes:

- maximum attachment count per note;
- maximum attachment size;
- blocked executable/script extensions;
- blocked active MIME types;
- generated storage paths rather than browser-provided paths.

Image files are stored as original uploaded bytes. The integration does not resize or recompress images.

---

## 8. Attachment preview/download flow

```mermaid
sequenceDiagram
    participant UI as Frontend panel
    participant WS as HA WebSocket
    participant M as NoteToSelfManager
    participant HTTP as Tokenized HTTP view

    UI->>WS: get_attachment_url(note_id, attachment_id, download?)
    WS->>M: check viewer can read note
    M-->>WS: short-lived attachment URL
    WS-->>UI: tokenized URL
    UI->>HTTP: GET /api/note_to_self/attachment/{token}
    HTTP->>M: resolve token
    HTTP-->>UI: file response
```

The URL can be used as inline preview or forced download using `download=1`.

---

## 9. Import/export flow

### Export

1. Frontend calls `note_to_self/prepare_export`.
2. Backend creates a short-lived export token for the active owner.
3. Browser downloads `/api/note_to_self/export/{token}`.
4. Export contains the owner's notes and attachment files.
5. Share metadata is intentionally not carried through export/import.

### Import

1. Frontend calls `note_to_self/prepare_import`.
2. Browser uploads a ZIP to `/api/note_to_self/import/{token}`.
3. Backend parses the ZIP and builds a duplicate preview.
4. Frontend asks the user to decide what to import/skip.
5. Frontend calls `note_to_self/confirm_import`.
6. Imported notes become private notes owned by the importing user.

---

## 10. Service actions

The integration exposes three Home Assistant service actions:

```text
note_to_self.create_note
note_to_self.update_note
note_to_self.delete_note
```

These require a user-backed Home Assistant service context. Anonymous or generic automation contexts are not the intended caller in this version.

---

## 11. V0.046 frontend lifecycle note

V0.046 intentionally removed the V0.044 shell-rebuild recovery path.

Reason:

- the automatic rebuild path could repeatedly rebuild the panel shell;
- repeated rebuilds could multiply event listeners;
- timer, resize, focus and refresh paths could amplify the issue into a browser freeze.

Current V0.046 behavior:

- panel shell initializes once;
- live refresh timers are cleaned up on disconnect;
- UI recovery logs and shows a banner instead of rebuilding the shell in a loop.

---

## 12. Upgrade checklist

When bumping the version:

1. Update `PANEL_VERSION` in `custom_components/note_to_self/const.py`.
2. Update `version` in `custom_components/note_to_self/manifest.json`.
3. Update `PANEL_VERSION` in frontend utility/module references if applicable.
4. Update `README.md` and `CHANGELOG.md`.
5. Hard reload the browser after installing the new version.
6. Verify that `/local/note-to-self/note-to-self-panel.js?v=<new-version>` is loaded.

---

## 13. Design rules to preserve

Do not trust the frontend for:

- current user identity;
- note ownership;
- share ownership;
- attachment storage paths;
- permission decisions;
- import ownership.

Keep these backend-owned:

- permission model;
- category share evaluation;
- thread ownership resolution;
- token generation and expiry;
- storage writes;
- blocked file validation.

