Data Contracts
Data-Contracts.mdData Contracts
Big Rule
runtime data lives under /data.
repo intent:
/datais not supposed to be versioned normally.gitignoreignores/data/**/*.rsyncignoreexcludes/data/**from deployment sync
so local/dev/prod data has to be managed separately.
data/accounts/
accounts.json
expected top-level shape:
{
"accounts": [
{
"username": "string",
"name": "string",
"password": "bcrypt-hash or empty",
"isAdmin": true,
"mustResetPassword": false,
"discordUserId": "optional discord snowflake string",
"allowedPages": ["feed", "journal", "comments", "chat"],
"bookmarks": ["2026-01-01_12-00-00", "journal:12"],
"theme": "default|classic|theme-id",
"glowIntensity": "none|low|medium|high",
"mobileFriendlyView": true,
"onekoEnabled": true,
"colors": {
"bg": "#RRGGBB",
"fg": "#RRGGBB",
"border": "#RRGGBB",
"subtle": "#RRGGBB",
"links": "#RRGGBB"
}
}
]
}
notes:
- extra unknown keys can exist and are preserved by
account/admin/edit - bookmarks are the current source of truth for logged-in users
- bookmark ids currently use raw feed ids and
journal:{id}; legacynewsletter:{id}values can exist but are ignored theme: defaultis blackprint and uses the base template plus/style.css;theme: classicenables savedcolors; any other valid value refers to a/themes/{theme-id}.jsonfile- legacy
blackprintnormalizes todefault,customnormalizes toclassic, andnewsprintnormalizes towhiteprint mustResetPasswordis used by the shared session bootstrap to force first-login password changesdiscordUserIdlinks a site account to a Discord member for bot DMs and notificationsallowedPagescurrently includes functional grants likefeed,journal,comments, andchat
data/chat/
one-time private conversations live as individual encrypted JSON envelopes:
{
"version": 1,
"cipher": "aes-256-gcm",
"nonce": "base64",
"tag": "base64",
"ciphertext": "base64"
}
notes:
- each file is named
{conversationId}.json; new chat ids are 9 lowercase letters/numbers - legacy 32-character lowercase hex ids are still accepted so older active links do not break
- decrypted payloads contain conversation metadata, the recipient label in
name, the recipient cookie hash, and message records - messages may include an
attachmentobject with encrypted blob metadata:id,name,mime, andsize; image/audio/video attachments are served inline through the authorized chat route so they can render or play in the chat UI - messages may include
replyTowith another message id, plusreactionskeyed by valid emoji sequences with active viewer roles such asmanagerorparticipant - conversations may include
participantUsernamewhen a logged-in account claims the invite, orparticipantHashwhen an anonymous browser cookie claims it; the first-open popup copy changes based on that claim type - conversations may include
recipientIntroSeenAtonce the recipient has seen the first-open security/help popup - recipient cookies are HttpOnly and scoped to
/chat - the first non-manager account or anonymous browser to open
/chat/{conversationId}claims the recipient slot - account-linked recipients can delete their own active chat; anonymous cookie-linked recipients cannot
- admins and accounts with
allowedPagescontainingchatcan create, view, and delete conversations without claiming the recipient slot - deleting a conversation unlinks the encrypted JSON file immediately
- encryption uses
FRIDG3_CHAT_KEYwhen set; otherwise the app createsdata/chat/.chat_key - lightweight presence indicators use sidecar files under
data/chat/.presence/{conversationId}.json; current entries storelastSeen,active, and a short-livedtypingUntil, while older timestamp-only entries are still readable - attachments are encrypted AES-256-GCM envelopes under
data/chat/.attachments/{conversationId}/; they are served only through the authorized chat route and are deleted with the conversation - attachment uploads are capped at 8 MB
/themes/
theme metadata lives as JSON files directly under /themes.
{
"name": "Theme Name",
"html": "template-file.html",
"css": "stylesheet-file.css"
}
notes:
- the metadata filename is the saved theme id, for example
/themes/cool.jsonbecomescool nameis the label shown in/settingshtmlandcssmust be relative paths in/themes/lib, for exampleaero/aero.htmlandaero/aero.css- theme asset paths cannot be absolute, contain
.., or use characters outside letters, numbers,.,_,-, and/ - desktop rendering uses both themed HTML and CSS
- mobile rendering keeps
template_mobile.htmland only swaps the CSS
login_attempts.json
- map of client IP -> unix timestamp array
- used for login throttling
data/feed/
feed post format:
@usernameYYYY-MM-DD HH:MM:SS- body text / BBCode
other file:
index.tomlis generated by/feed/index.php
feed bodies can include public voice notes as BBCode:
[audio=/data/audio/voice/example.m4a][name:voice-note.m4a]
voice notes are created from temporary [voice:N] editor placeholders, verified at upload time, transcoded to small mono .m4a files, and stored under data/audio/voice/.
data/feed/replies/
per-post replies live in {postId}.json files shaped roughly like:
{
"replies": [
{
"id": "20260413153000_deadbeef",
"username": "toast",
"date": "2026-04-13 15:30:00",
"body": "reply body with BBCode"
}
]
}
notes:
- reply ids are generated on write; older data may be normalized into
legacy_*ids at read time - reply bodies can contain image BBCode that points at
/data/images/* - new reply bodies can also contain voice note audio BBCode that points at
/data/audio/voice/* - automatic Toast replies are stored as normal
username: "toast"replies when a user replies to Toast's post or mentions@toast; generated Toast replies begin by mentioning the triggering user and are delayed by 1 minute before posting
data/journal/
published journal post:
YYYY-MM-DD- title
- description
- trusted HTML body
draft format:
USER:<username>- title
- description
- optional
FORMAT:html - draft body
without FORMAT:html, preview treats the body as BBCode. with it, preview treats the body as raw HTML.
data/guestbook/
entry format:
- timestamp
- display name
- message body
plus:
ip_index.jsonfor one-post-per-IP ownership tracking
data/images/
- uploaded images used across feed, journal, and gallery content
- expected web path is
/data/images/<filename>
data/music/
artist folders currently include:
frdg3cactile
album JSON shape:
{
"album_name": "string",
"album_caption": "string",
"album_type": "Album|EP|Single|Remix|...",
"album_art": "/data/images/example.jpg",
"album_art_directory": "/data/images/example.jpg",
"order": 6,
"songs": [
{ "name": "Track", "directory": "/data/audio/file.wav" }
]
}
album_art_directory is preferred by current code.
data/audio/
- track files referenced by music metadata
- also used by shared playback features
data/audio/voice/stores public feed voice notes as compressed.m4afiles
data/contact/
- private contact submissions as
{YYYYMMDDHHMMSS}_{random}.json - each submission stores
id,createdAt, hashed IP, user agent, name, email, message, notification channel id, and optionalnotifyError rate_limits.jsonstores hashed client IP keys mapped to recent submission timestamps for throttling- nginx blocks direct web access to this directory; submissions are only shown through the admin-only
/contact?dashboard=1route
data/mdpaste/
- temporary markdown paste records as
{id}.json - ids are 16 lowercase hex characters
- records expire after 30 days and are cleaned up opportunistically on create/view
- unencrypted records store a
markdownstring - encrypted records store only AES-256-GCM ciphertext plus PBKDF2-SHA256 salt/nonce/tag metadata; the password is never stored
hard_breakscontrols whether single paragraph newlines render as<br>instead of spaces
data/etc/
wip
- plain text maintenance flag
webhooks.json
used key:
{
"discord_feed": "https://discord.com/api/webhooks/..."
}
toast.json
expected shape:
{
"bot": { "token": "...", "client_id": "...", "status": "online|offline" },
"stream": { "url": "http(s)://...", "name": "..." },
"channel": { "id": "...", "name": "..." },
"features": { "auto_play": true, "loop": true },
"groq": {
"api_key": "...",
"model": "llama-3.1-8b-instant",
"website_model": "llama-3.3-70b-versatile",
"vision_model": "meta-llama/llama-4-scout-17b-16e-instruct",
"temperature": 0.8,
"top_p": 0.95,
"max_completion_tokens": 700,
"timeout_seconds": 30,
"max_history_messages": 12,
"max_vision_images": 5
}
}
groq powers Toast's AI replies to direct messages, the hardcoded Toast feed generator, and automatic Toast feed replies. If api_key is empty, Toast still logs inbound DMs but skips the AI reply, the feed generator returns an error instead of drafting, and automatic feed replies are skipped. model defaults to llama-3.1-8b-instant and is used by the Discord Toast DM flow. website_model defaults to llama-3.3-70b-versatile and is used by website Toast feed generation and automatic feed replies; legacy feed_model is also accepted as a fallback name. Image/GIF DMs use vision_model, defaulting to Groq's meta-llama/llama-4-scout-17b-16e-instruct. max_history_messages controls how many recent logged DM messages are sent as conversation context, and max_vision_images caps image attachments at Groq's 5-image request limit. Toast also sends Groq a compact summary of its bot duties, including radio playback, slash-command radio controls, account-linking support, and automated notification DMs. When a DM appears to ask about fridg3.org, Toast can also send small relevant context from wiki/Home.md and wiki/Routes-and-Features.md to Groq so replies can describe the site without sounding like developer docs.
Website feed generation adds stricter feed-only voice limits on top of the editable personality: Toast feed posts and automatic replies should stay short, closer to old-style Twitter posts than assistant answers. Automatic replies are capped after cleanup so an ignored prompt cannot turn into a forum-moderator essay.
Website and Discord AI prompts also append an identity anchor after loading editable personality JSON: Toast should describe themself as a humanoid fridg3.org resident whose closest relative is the homosapien, not as a bot, AI, assistant, agent, model, or program.
The website Toast feed generator and auto replies add feed-only wording rails to reduce repetitive openings like just did, just made, just got, just found, or just realized. Website feed post generation accepts a 1-5 length setting with deliberately strong steps: one-liner, short, normal, ramble, and trauma dump. The selected length applies both prompt instructions and cleanup caps, with max allowing a much longer vulnerable post while the shortest setting is forced down to a single short sentence. Feed generation sends only a small weak style sample from already-published non-Toast feed posts, never unpublished generated drafts, and instructs Groq not to reuse those samples' topics or phrasing. Recent published Toast posts are sent only as negative examples to avoid repeating their topics, imagery, openings, or emotional arc. Each generation also gets a private freshness seed with a random creative angle, texture, and anti-pattern so repeated clicks vary more. Toast feed posts are instructed to be self-contained personal thoughts rather than conversation starters, so they should not ask readers for feedback, replies, comments, validation, or suggestions, and should not acknowledge audience size or being alone. The website no longer stores its own Groq cooldown state; Groq 429s are returned directly after any short one-shot retry.
AI DM replies are split into sentence-aware Discord messages, usually 2-4 sentences per send depending on sentence length, while still staying below Discord's hard message limit. Toast waits at least 5 seconds before every AI reply chunk so the visible typing state never flashes and instantly dumps a response. Each Discord user has one active AI reply task: if another DM arrives while Toast is generating or pacing an unsent chunk, Toast cancels the unfinished reply and regenerates from the queued inbound DMs combined into one chronological prompt.
Toast's AI prompt includes an exact Discord slash-command allow-list: /play, /stop, /status, and /sendmsg. Website paths such as /feed must be described as fridg3.org pages, not Discord slash commands.
toast-personality.json
shared editable Toast personality source:
{
"discord": {
"system_prompt": "core Discord Toast personality instructions",
"style_rules": ["optional behavior/style rule"],
"do_not": ["optional constraint"],
"private_lore": "optional guarded lore"
},
"feed": {
"system_prompt": "core feed-writing Toast personality instructions",
"style_rules": ["optional behavior/style rule"],
"do_not": ["optional constraint"],
"private_lore": "optional guarded lore"
}
}
/settings exposes this JSON only to the hardcoded toast session. Both discord and feed must be objects with non-empty system_prompt values. If the file is missing, website code seeds both blocks from others/toast-discord-bot/bot/personality.json in memory.
others/toast-discord-bot/bot/personality.json
expected shape:
{
"system_prompt": "core Toast personality instructions",
"style_rules": ["optional behavior/style rule"],
"do_not": ["optional constraint"],
"private_lore": "optional lore that Toast only shares when directly asked"
}
legacy Discord bot fallback. The bot now prefers data/etc/toast-personality.json and uses this file only when the shared file is missing or lacks a usable discord block. If both are missing, empty, or invalid, the bot logs a warning and uses a small built-in Toast fallback prompt. private_lore is included in the system prompt with an explicit guardrail to avoid volunteering it unless the user asks about Toast's origin, lore, backstory, life, or purpose.
toast-updates.json
- array of timestamped bot status entries
toast-feed-notify-state.json
- internal bot dedupe state for sent feed mention/reply notifications
- stores which feed post mentions, feed reply mentions, and replies to a user's own posts have already triggered DMs
toast-dm-history.json
- tracked inbound/outbound DM threads used by
/others/toast-discord-bot/messages - stores per-user profile snapshot data, optional
ai_mutedreply-suppression state, plus message history - an inbound DM containing exactly
CLEARMEMORYacts as a memory boundary for AI replies; future Groq context only includes messages after the newest boundary
contact notification endpoint
- the toast bot exposes localhost-only
POST /contact/notifyon127.0.0.1:8765 /contactcalls it after saving a submission- toast sends the alert to Discord channel
1503931489560301609
off-topic-archive.json
- Discord export blob used by the archive viewer
page_views.json
shape is roughly:
{
"pages": {
"/": {
"count": 12,
"visitors": {
"<sha256>": 1730931224
}
}
},
"updated_at": "2026-03-02T00:00:00Z"
}
data/downloads/
- downloadable binaries, archives, presets, and similar files linked from the site