Sweeping rename across crates, CLI binary, WASM bindings, extension, docs,
and vault metadata paths. Git remote updated to relicario.git.
- crates/idfoto-{core,cli,wasm} -> crates/relicario-{core,cli,wasm}
- IdfotoError -> RelicarioError
- IDFOTO_IMAGE env var -> RELICARIO_IMAGE
- ~/.config/idfoto -> ~/.config/relicario
- .idfoto/ vault metadata dir -> .relicario/ (breaking; pre-release)
- Binary name idfoto -> relicario
- Extension wasm module idfoto_wasm -> relicario_wasm
- Storage key idfotoSettings -> relicarioSettings
- All doc filenames and content references updated
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.3 KiB
relicario — Standalone Vault Initialization Wizard Design
A browser-based wizard that guides new users through creating an relicario vault from scratch. Lives at extension/setup.html, uses the same WASM module as the extension, same terminal dark aesthetic. No server, no Rust toolchain required.
Scope
- Single HTML page with inline JS (bundled by webpack) at
extension/setup.html - 4-step wizard: choose host → configure connection → create vault → finish
- Pushes vault files directly to Gitea/GitHub via API
- Downloads reference image to user's machine
- Optionally pushes config to the Chrome extension if installed
Flow
Step 1: Choose Git Host
Toggle between Gitea and GitHub. Below the toggle, show inline setup instructions:
Gitea instructions:
- Log in to your Gitea instance
- Create a new empty repository (no README, no .gitignore)
- Go to Settings → Applications → Generate New Token
- Select scope:
repo(read/write) - Copy the token
GitHub instructions:
- Go to github.com → New Repository
- Create an empty repository (no README, no .gitignore, no license)
- Go to Settings → Developer Settings → Personal Access Tokens → Fine-grained tokens
- Generate new token, select only the target repository
- Permissions: Contents → Read and write
- Copy the token
Step includes a "Next" button. No validation needed at this step.
Step 2: Configure Connection
Fields:
- Host URL (e.g.
https://git.adlee.workorhttps://github.com) — pre-filled based on host type selection - Repository path (e.g.
alee/relicario-vault) - API token (password field)
"Test Connection" button:
- Hits the git API to verify the token works and the repo exists
- Checks that the repo is empty (no files) or contains only a README
- Shows green checkmark on success, red error on failure
- Must pass before "Next" is enabled
Uses the same GitHost interface (GiteaHost/GitHubHost) from the extension's service worker code.
Step 3: Create Vault
Two inputs:
- Carrier image: File picker for a JPEG. Shows preview thumbnail after selection. Minimum size guidance ("use a photo from your phone — at least 400x300").
- Passphrase: Password field with confirmation. Minimum 8 characters enforced. Shows basic strength indicator (weak/ok/strong based on length + character variety).
"Create Vault" button triggers:
- Load WASM module
- Generate random 32-byte
image_secretviacrypto.getRandomValues() - Embed secret into carrier JPEG via WASM
extract_image_secret— wait, that's extract. We needembed. Check: the WASM crate currently only exposesextract_image_secret, notembed. We need to add aembed_image_secretfunction torelicario-wasm. - Generate random 32-byte
saltviacrypto.getRandomValues() - Create
params.jsonwith default KDF params ({"argon2_m":65536,"argon2_t":3,"argon2_p":4}) - Derive
master_keyvia WASMderive_master_key(passphrase, image_secret, salt, params_json) - Encrypt empty manifest (
{"entries":{},"version":1}) via WASMencrypt_manifest - Push files to repo via git API:
.relicario/salt(raw 32 bytes).relicario/params.json(JSON string).relicario/devices.json([])manifest.enc(encrypted manifest bytes)
- Show progress bar during push operations
Spinner/progress during the Argon2id derivation (~1-2 seconds) and API calls.
Step 4: Finish
Two things happen:
Download reference image:
- Browser downloads the steganographic JPEG as
reference.jpg - Show warning: "Keep this image safe. You need it alongside your passphrase to unlock the vault. Store it somewhere you won't lose it."
Push config to extension (if available):
- Try to detect the relicario extension via
chrome.runtime.sendMessagewith aget_setup_statemessage - If extension responds: push
save_setupmessage with{ config: { hostType, hostUrl, repoPath, apiToken }, imageBase64 }. Show "Extension configured! You can now open the extension and unlock your vault." - If extension not detected: show the config as a copyable JSON blob with instructions: "Install the relicario extension, then paste this into the setup wizard." (Or just tell them to run through the extension setup manually with the same host/token/repo.)
WASM Crate Change
The relicario-wasm crate needs one new function:
#[wasm_bindgen]
pub fn embed_image_secret(carrier_jpeg: &[u8], secret: &[u8]) -> Result<Vec<u8>, JsValue>
This wraps relicario_core::imgsecret::embed. Currently only extract_image_secret is exposed.
File Structure
extension/
├── setup.html # standalone wizard page
├── src/
│ └── setup/
│ └── setup.ts # wizard logic (4-step state machine)
├── webpack.config.js # add 'setup' entry point
The setup page reuses:
extension/wasm/— same WASM moduleextension/src/service-worker/git-host.ts,gitea.ts,github.ts— git API layerextension/src/popup/styles.css— terminal dark theme (imported or linked)extension/src/shared/types.ts— VaultConfig type
UI Design
Same terminal dark aesthetic as the popup but in a full-page layout (not 360px constrained). Centered content area, max-width ~600px. Same color scheme (#0d1117 bg, #58a6ff blue, monospace font).
Progress bar at top showing step 1-4. Each step is a full-page view with back/next navigation.
Extension Detection
// Try to send a message to the extension
function detectExtension(): Promise<boolean> {
return new Promise((resolve) => {
try {
chrome.runtime.sendMessage(
{ type: 'get_setup_state' },
(response) => {
if (chrome.runtime.lastError || !response) {
resolve(false);
} else {
resolve(true);
}
}
);
} catch {
resolve(false);
}
});
}
Note: this only works if setup.html is served from the extension itself (chrome-extension://<id>/setup.html) or if we use externally_connectable in the manifest. For a local file, extension detection won't work — fall back to manual config copy.
If we add setup.html to the extension's web_accessible_resources and the user opens it via chrome-extension:// URL, messaging works natively.
manifest.json Changes
Add setup.html to the extension so it can be opened as a chrome-extension page:
{
"web_accessible_resources": [{
"resources": ["setup.html", "setup.js", "styles.css", "relicario_wasm_bg.wasm", "relicario_wasm.js"],
"matches": ["<all_urls>"]
}]
}
The setup page can then be opened at chrome-extension://<extension-id>/setup.html. The extension popup can link to it, or the user can navigate directly.
Security
- Passphrase never leaves the browser
- image_secret generated client-side, embedded client-side, never transmitted
- master_key derived and used in-browser only, then discarded
- API token used only for pushing vault files and optionally passed to extension storage
- The reference image download is the only artifact the user needs to keep safe
Non-Goals
- Creating repos via API (user creates the repo manually — API permissions for repo creation vary widely)
- Git operations beyond file CRUD (no commits history, no branches)
- Password strength estimation beyond basic length/variety checks
- Mobile support (desktop Chrome only for now)