Files
sovereign_browser/plans/embedded-web-content.md

9.1 KiB

Embedded Web Content — sovereign:// pages from files

Problem

The sovereign://agents/chat page is hardcoded as a ~800-line C string literal in src/nostr_bridge.c (handle_agents_chat_page()). This causes:

  • Bugs — C string escaping errors (\", \\, %%) are easy to make and hard to spot. The current "messages don't render" and "New Chat doesn't work" bugs are likely caused by this.
  • Maintenance nightmare — No syntax highlighting, no IDE support, no formatting tools work on the embedded HTML/CSS/JS.
  • Can't copy ai.html — The user wants to port ~/lt/client/www/ai.html's structure, but that's a standalone HTML file with separate CSS/JS. Embedding it as a C string would be thousands of lines of escaped strings.

Solution: Copy c-relay's approach

~/lt/c-relay solves this with a build-time embedding script:

  1. Author web files as normal files in a www/ directory (HTML, CSS, JS — no escaping needed, full IDE support).
  2. embed_web_files.sh — A shell script that runs at build time. It uses hexdump to convert each file into a C byte array (0x3c,0x21,0x44,...) in an auto-generated src/embedded_web_content.c.
  3. embedded_web_content.h — Declares embedded_file_t and get_embedded_file(path).
  4. embedded_web_content.c — Auto-generated byte arrays + a lookup table mapping paths to arrays.
  5. The sovereign:// handler calls get_embedded_file() to serve the embedded content instead of building HTML with g_strdup_printf().

Architecture

┌─────────────────────────────────────────────────────────────┐
│  www/                    (normal files, edit freely)         │
│    agents/chat.html      (standalone HTML)                   │
│    agents/chat.css       (separate CSS)                      │
│    agents/chat.js        (separate JS)                       │
│    agents/config.html    (provider config page)              │
│    agents/config.css                                        │
│    agents/config.js                                         │
│    settings.html                                            │
│    bookmarks.html                                           │
│    ...                                                      │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼  embed_web_files.sh (build time)
┌─────────────────────────────────────────────────────────────┐
│  src/embedded_web_content.c   (auto-generated, do not edit)  │
│    static const unsigned char agents_chat_html_data[] = {    │
│      0x3c,0x21,0x44,0x4f,0x43,0x54,0x59,0x50,0x45,...        │
│    };                                                       │
│    static const size_t agents_chat_html_size = 12345;        │
│    ...                                                      │
│    static embedded_file_t embedded_files[] = {               │
│      {"agents/chat.html", agents_chat_html_data, ...},       │
│      {"agents/chat.css",  agents_chat_css_data,  ...},       │
│      {"agents/chat.js",   agents_chat_js_data,   ...},       │
│      {NULL, NULL, 0, NULL}                                   │
│    };                                                       │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼  get_embedded_file(path)
┌─────────────────────────────────────────────────────────────┐
│  src/nostr_bridge.c  (sovereign:// handler)                  │
│    on_sovereign_scheme():                                    │
│      if (strncmp(uri, "sovereign://agents/chat", ...) == 0)  │
│        serve_embedded_file(request, "agents/chat.html");     │
│      ...                                                     │
└─────────────────────────────────────────────────────────────┘

Implementation plan

Phase 1 — Set up the embedding infrastructure

  1. Create www/ directory at the project root for web files.

  2. Create embed_web_files.sh (copy from c-relay, adapt for our file structure). It scans www/**/*.html, www/**/*.css, www/**/*.js and generates src/embedded_web_content.c + src/embedded_web_content.h.

  3. Create src/embedded_web_content.h:

    typedef struct {
        const char *path;          /* e.g. "agents/chat.html" */
        const unsigned char *data;
        size_t size;
        const char *mime_type;     /* "text/html", "text/css", "application/javascript" */
    } embedded_file_t;
    
    const embedded_file_t *get_embedded_file(const char *path);
    
  4. Add a serve_embedded_file() helper in nostr_bridge.c that calls get_embedded_file() and responds with the bytes via respond_bytes().

  5. Update Makefile to run embed_web_files.sh before compiling, and add src/embedded_web_content.c to SRC.

  6. Update .gitignore to ignore src/embedded_web_content.c and src/embedded_web_content.h (they're auto-generated).

Phase 2 — Migrate the chat page to files

  1. Create www/agents/chat.html — Extract the HTML structure from handle_agents_chat_page() into a standalone HTML file. No C string escaping needed.

  2. Create www/agents/chat.css — Extract the CSS into a separate file. Link it from the HTML: <link rel="stylesheet" href="sovereign://agents/chat.css">.

  3. Create www/agents/chat.js — Extract the JavaScript into a separate file. Link it: <script src="sovereign://agents/chat.js"></script>.

  4. Add routes in on_sovereign_scheme():

    • sovereign://agents/chat → serve agents/chat.html
    • sovereign://agents/chat.css → serve agents/chat.css
    • sovereign://agents/chat.js → serve agents/chat.js
  5. Fix the bugs while extracting:

    • Messages not rendering: The renderMessage() function must include data-msg-index="N" on .msg-bubble-content and .msg-bubble-menu-host elements so renderBubbleContent() and mountDotMenu() can find them.
    • New Chat not working: Verify sovereign://agents/conversations/new creates a new session and sets it as active. The newChat() JS must clear the message list and reset lastCount = -1.

Phase 3 — Migrate other sovereign:// pages

Once the chat page works, migrate the other pages the same way:

  • sovereign://agents (config page) → www/agents/config.html + .css + .js
  • sovereign://settingswww/settings.html + .css + .js
  • sovereign://bookmarkswww/bookmarks.html + .css + .js
  • sovereign://profilewww/profile.html + .css + .js

Each page keeps its C-side data endpoints (sovereign://agents/set, sovereign://agents/messages, etc.) but the page HTML/CSS/JS moves to files.

Phase 4 — Port ai.html features

With the chat page as a standalone HTML file, we can directly copy:

  • The ai.html message bubble CSS (from messaging-ui.css)
  • The dot-menu CSS (from dot-menu.css)
  • The markdown renderer (use marked.js from the client's vendor libs, or keep our minimal renderer)
  • The conversation list layout
  • The skills list layout
  • The provider config sidenav

Benefits

  • No C string escaping — Edit HTML/CSS/JS normally with full IDE support
  • Syntax highlighting — IDEs recognize .html, .css, .js files
  • Can copy ai.html directly — Just copy the relevant sections
  • Smaller nostr_bridge.c — The 800-line chat page string literal is gone
  • Easier debugging — Browser dev tools show the actual HTML, not escaped strings
  • Build-time embedding — Files are bundled in the binary, no external files needed at runtime

Files to create

File Purpose
embed_web_files.sh Build script: converts www/ files to C byte arrays
src/embedded_web_content.h Header for the embedded file lookup
src/embedded_web_content.c Auto-generated byte arrays (gitignored)
www/agents/chat.html Chat page HTML
www/agents/chat.css Chat page CSS
www/agents/chat.js Chat page JS

Files to modify

File Change
Makefile Run embed script, add embedded_web_content.c to SRC
.gitignore Ignore auto-generated embedded_web_content.*
src/nostr_bridge.c Add serve_embedded_file(), route to embedded files, remove old handle_agents_chat_page() string literal