Monorepo for Aesthetic.Computer aesthetic.computer
core system backend MIME.md
13 kB
Markdown
at main

Mime #

Discussion on AC media. The frontend is /mime/ and the https://mime.ac/ apex; the API is /api/mime. www.mime.ac redirects to the apex. The main feed is one responsive column of media cards, with contained media, author-colored handles and avatars, comment controls, and the first comment preview. Letterboxing uses a slowly drifting, author-tinted checkerboard; reduced motion keeps it still. It loads 12 posts at a time and preserves the current card and offset across thread visits and orientation changes. Videos play muted inline and pause offscreen. A load-more button supports manual loading and retries. The feed has no category bar, file composer, or footer. Each post shows its full MIME type and a bookmark ribbon. Saved favorites live in browser storage as post IDs; the Saved view rechecks current public visibility and supports removal. Failed media previews show an explicit fallback link. ZIP tapes remain downloads; MP4 tapes autoplay muted while visible and pause offscreen. /mimechan/ preserves old hash links through a redirect, and /api/mimechan remains an alias. Existing uploaded files and replies stay in mimechan.

Media identity #

Public paintings, tapes, pieces, and kidlisp records act as opening posts. Their thread keys are <kind>_<Mongo ObjectId>. Codes, handles, filenames, and tape conversions can change without changing the discussion. Media without short codes can be addressed by ObjectId.

Reads combine native uploads with the current source records. No backfill or upload hook is required, and source files are not copied. mime-media-threads stores only reply counts and bump times, starting with the first reply.

Because thread keys are Mongo _ids, a media record that exists only outside Mongo is unaddressable here, not merely unlisted. That is what the KIDLISP_DATOMIC cutover caused: KidLisp writes moved to Datomic, and every piece made after it went missing from this feed and /api/tv — 766 pieces before it was caught, 293 of them handle-owned, the oldest from 2026-04-20. (Oven's thumbnails were never affected: /grab/ drives a headless browser against the live site, so it resolves a piece the same way a visitor does.) backend/kidlisp-projection.mjs now writes an identity-only row for each new piece so it has an _id to be addressed by; backend/kidlisp-backfill.mjs repairs the gap. Nothing mutable is mirrored, so hits stays Datomic's and sort=hits ranks a post-cutover piece as 0 until it is read through /api/store-kidlisp.

Records marked nuked, deleted, hidden, private, draft, or with a non-public visibility are excluded. Legacy records without a visibility field follow the existing public AC feeds. Private upload drafts live in a separate collection and are never queried. Removed media threads and their reply attachments return 404; restoring the original record restores its discussion.

API #

  • GET /api/mime — boards and recent opening posts, with up to two text-only preview entries (name, text) per commented post; page=0 by default.
  • GET /api/mime?board=image/png&page=0 — 12 threads, three recent replies each.
  • GET /api/mime?media=painting&code=abc — resolve a public media code to its thread.
  • GET /api/mime?thread=painting_<id> — original media and replies.
  • GET /api/mime?file=painting_<id> — resolve current source bytes or storage URL.
  • GET /api/mime?me=1 — verified handle for the supplied bearer token.
  • POST /api/mime — { parent, text, name, file }. A null parent requires a file; a reply requires text and rejects files. Opening-post files use { name, type, data } with base64 bytes and an 8 MiB limit. Replies keep their parent's board. The first 300 replies bump a thread.

Source metadata comes from AC records, not request-supplied URLs or attribution. Signed-in posts store the verified AC account and display its current handle. The composer reuses AC’s same-origin Auth0 session (SDK keys use :: separators) or its encoded session-aesthetic host session. Both are verified by the API; invalid sessions cannot fall back to posting anonymously. Guest names remain unverified. Existing comment attachments remain readable, but new comments are text-only. kidlisp and piece records are programs AC can run, so their cards boot the real runtime in a cross-origin iframe once the card is the focused one — KidLisp addressed as $code, a piece by its bare code. The frame carries noauth, which matters because a feed boots these unattended: a card can never act as the signed-in viewer. Autoplay, camera and microphone stay denied, and only one card runs at a time. KidLisp cards show an oven thumbnail until they boot; pieces get none, because oven returns a uniform black frame for every piece in the collection with a 200 rather than an error, so they wait on the checkerboard rather than sit on a black square. Whether that is oven's limit is untested — every pieces record today is a broken MCP fixture for which black is the correct render. Uploaded program files are still displayed as text, and any record's source stays readable at ?file=. Native HTML previews remain sandboxed. Painting and tape metadata endpoints expose discussion URLs for other clients.

Direct entry: /mime/#/media/painting/abc (also tape, piece, or kidlisp). The painting viewer includes a Comment button. Other media can be discussed from the main feed or a direct entry URL. Entering mime in the prompt opens the feed. The corner word returns to /prompt.

Legacy piece records without an extension retain the existing JavaScript default. Both upload paths now preserve the extension for future records. Rate limiting and moderation tools for anonymous replies remain follow-up work.

Posting from a terminal #

ac mime <file> ["caption"] posts a file as an opening post under the signed-in @handle (~/.ac-token), on the board of its MIME type, and prints https://mime.ac/#/t/<code>. AESEL_DRY_RUN=1 shows what would be posted. In Aesel, the media name on the status line opens a list with "post to mime.ac", which asks once more before posting. The client is aesel/src/mime.mjs; it checks size and type before any bytes leave. Files over 8 MiB are refused until opening posts can carry a storage URL rather than inline bytes.

Validation #

Use a disposable loopback MongoDB; the tests refuse remote hosts and create their own temporary database:

MIME_TEST_MONGO_URI=mongodb://127.0.0.1:27017 node --test system/backend/tests/mime.test.mjs

Tests cover automatic discovery without writes, attribution, concurrent first replies, attachment rejection, verified authorship, source visibility, storage URLs, legacy uploads, pagination, and identity across tape conversion.

Auth0 domains #

MIME uses the existing AC SPA client (LVdZaMbyXctkGfZDnpzDATB5nR0ZhmMt) through hi.aesthetic.computer. Keep all existing application URL entries; append these exact values in the Auth0 application settings:

  • Allowed Callback URLs: https://mime.ac/, https://aesthetic.computer/mime/
  • Allowed Logout URLs: https://mime.ac/, https://aesthetic.computer/mime/
  • Allowed Web Origins and Allowed Origins (CORS): https://mime.ac

The SDK handles authorization-code callbacks and validates OAuth state. The return hash and an in-progress comment survive the login redirect. Account handles are still resolved by the API from the verified access token. Browser storage is origin-scoped, so MIME offers sign-in on the new domain; it does not copy tokens between domains. The wordmark returns to AC's prompt.

Name and history #

The name refers to MIME media types (image/png, audio/ogg, text/plain). Content-Type predates MIME: RFC 1049 proposed it for structured Internet messages in March 1988. Nathaniel Borenstein and Ned Freed's RFC 1341, June 1992, extended mail to carry multiple parts, richer character sets, images, audio, and other data. The familiar RFC 2045 is a November 1996 revision, not MIME's starting date.

An early HTTP working draft uses MIME representations and discusses replies and separate annotation stores. That is a conceptual precedent for discussion attached to media, not a claim that MIME itself specifies comment threads.

In this repository, 7f0af85c25 (September 2, 2026) introduced Mimechan: each uploaded file's MIME type selected its board. 0c27619279 (September 15) made MIME a discussion layer over existing public AC media. The media records provide the subjects; comments attach to their stable identities.

Each extruded letter floats, changes lighting, and gently transforms on its own timing; reduced-motion preferences disable these animations. The .ac suffix uses YWFT Processing. The wordmark borrows its proportions and slab serifs from early workstation typography. Andrew Messages screenshots from 1994 show bitmap controls, serif correspondence, and embedded images in one mail reader. This is a new AC mark, not a reproduction of an official MIME logo. Borenstein's reconstructed March 1992 demo provides another reference: a photograph and audio carried within a message.

Tab and share assets #

The browser title cycles between Unicode lettering styles every 1.25 seconds, following Oskiewar's tab rhythm. It returns to plain mime.ac when hidden or when reduced motion is enabled. The HTML retains a plain title for crawlers. SVG/PNG favicons, an Apple touch icon, and a 1200×630 JPEG share card live in system/public/mime/; Caddy serves their paths directly on mime.ac. Open Graph and Twitter metadata use absolute public image URLs.

Rebuild the assets from the inline wordmark with node system/scripts/build-mime-brand.mjs (set CHROME_PATH on hosts whose Chromium executable is elsewhere). The build uses local bundled fonts.

Feed engagement metadata #

The feed uses native document scrolling, including gestures over the margins and header. The sticky header shrinks and centers the logo after scrolling; reduced motion disables that transition. The account/profile control stays bottom-right, with bottom padding to keep the last content reachable. Visibility measurements exclude the sticky header.

The continuous scrolling feed measures post visibility while the document is visible and the browser window has focus. The post with the largest visible area is focused; distance to the viewport center breaks ties. DOM attributes expose data-focused-post on the feed and data-visible-ratio / data-focused on cards.

POST /api/mime?engagement=1 receives cumulative per-post counters under a random, in-memory page-visit ID. Mongo's mime-engagement collection uses that ID plus the post code as its key; $max makes retries and out-of-order delivery idempotent. Only existing public roots qualify. The collector sends no account identifiers, comment contents, URLs, cookies, or persistent device identifiers. Request fields are allowlisted; batches have at most 24 posts and durations cap at 24 hours per post/visit. The existing post/reply collections remain unchanged.

Thread responses expose aggregate metadata.engagement (version 1):

  • visibleMs: any part of the card visible.
  • partialMs / majorityMs: below 50% / at least 50% of the card visible.
  • focusedMs: time as the foreground feed's dominant card.
  • weightedVisibleMs: duration multiplied by visible fraction.
  • maxVisiblePermille: greatest fraction visible, from 0 to 1000.
  • impressions: page visits with at least one second of majority visibility.
  • commentOpens / originalOpens: clicks on the feed's comment/original actions.

Timers sample each second and on scrolling, with a two-second maximum elapsed sample to exclude suspended-browser gaps. Counters flush every 15 seconds and on navigation/backgrounding; failures retry while the page remains open. Delivery on page exit is best effort. These are client-reported exposure estimates, not measured gaze, unique people, or fraud-resistant metrics. Multiple posts may accrue visible time simultaneously; only one accrues focused time. Thread reading time is not included. The feed interleaves paintings, tapes, KidLisp, pieces, and native uploads, newest first within each kind. These counters do not change ranking.

KidLisp cards show lazy-loaded WebP previews and run an embedded AC player only when their media frame is the most visible one in the foreground window. The player unloads on focus change or navigation; a 350 ms delay avoids booting programs while scrolling past. The MIME label continues to describe the source.