Files
plspace/README.md
T
2026-07-30 20:31:34 +09:00

5.0 KiB

plspace

It's always Pleroma.

A static, client-side frontend for Pleroma that looks and behaves like MySpace circa 2005.

This is a 100% Claude and ChatGPT vibe-coded application.

Point it at your server. It talks to that server's REST API directly from the browser — there is no plspace backend, and dist/ is a folder of static files you can host anywhere.

Repository

The canonical repository is hosted on Radicle: rad://z2gAKC6ESt5ZBV419uVPf2vFtEHCT.

What it looks like

MySpace plspace
Profile page: photo, headline, vitals, Contacting box, Interests table #/@user@server
Latest Blog Entries with "(view more)" Account statuses, headline list
Blurbs: "About me", "Who I'd like to meet" The bio, split on a heading if you wrote one
Friend Space grid Followers
Mail Center, Friend Request Manager Notifications, follow requests
Bulletin Space The local timeline
Kudos Favourites
Custom profile layouts CSS in a profile field named css

Running it

npm install && npm run dev
npm run build

npm run build type-checks and then bundles to dist/. Because routing is hash-based, the output works from any path — a subdirectory, an S3 bucket, GitHub Pages — with no server rewrite rules.

Signing in

plspace registers itself as an OAuth app on your server the first time you sign in there, then redirects you to that server's own consent screen. Your password is never entered into plspace; it only ever receives OAuth tokens, which are stored in localStorage and used directly from your browser. Short-lived access tokens are renewed with a server-issued refresh token when available.

PKCE is used where the server supports it, with an automatic fallback for servers that don't.

You can also browse without signing in — click Just look around. Note that many servers set restrict_unauthenticated and refuse timeline reads from anonymous callers; the suggested servers on the sign-in page are ones verified to allow it.

Making it yours

Settings has a CSS editor, five starter layouts, and a full class reference. Everything about the appearance is overridable — see src/styles/README.md for the contract. In short: no CSS framework, no Svelte scoped styles, no hashed class names, every colour and metric is a custom property, and user CSS is always last in the cascade so plain single-class selectors win without !important.

There is no built-in dark mode. A dark theme is just token overrides, so it ships as a preset (Midnight, Terminal) you can apply, edit or replace, rather than as a toggle you can't.

To publish a layout other people see on your profile, put CSS in a profile field named css on your server. It is scoped to your profile's subtree and filtered before it is applied.

Your profile fields also drive the profile page: name one Music, Movies, Television, Books or Heroes and it fills the Interests table; name one Mood, Location, Gender or Headline and it fills the block beside your photo.

Layout

src/
  lib/
    api/         client (fetch, Link-header pagination), OAuth, endpoints, entity types
    stores/      session, Feed (cursor pagination), theme (user + profile CSS)
    util/        HTML sanitizing, 2005-flavoured date formats, account -> profile mapping
    router.svelte.ts
    themes.ts    starter layouts, written only in terms of tokens
  components/
    chrome/      header, nav, footer
    common/      Module, RichText, Avatar, Pager, TabBar
    profile/     identity, contacting box, interests, details, friend space
    blog/        entry, attachments, poll, preview card, composer
    people/      person row and list
  routes/        Home, Profile, Timeline, StatusPage, Mail, Browse, Search, Compose, Login, Settings
  styles/        tokens, base, layout, chrome, module, profile, blog, forms

Safety notes

All HTML from the API — post content, bios, profile field values — is sanitized with DOMPurify before it reaches {@html}, in exactly one component (RichText.svelte) plus the profile-field helper. Mention and hashtag links are rewritten to in-app routes; every other link gets target="_blank" with rel="noopener noreferrer".

Compatibility

Written against the standard /api/v1 REST API and the local Egregoros implementation, as well as live servers. Anything implementations differ on degrades rather than fails: /api/v2/instance falls back to v1, /api/v1/accounts/lookup falls back to search, pagination falls back to the last item's id when a server drops the Link header, and per-account privacy flags are read in both their spellings. Egregoros' hour-long access tokens and rotating refresh tokens are supported; notification folders are filtered defensively because its current API ignores types[]. Profile controls are capability-aware, so unsupported Egregoros fields are not shown as though they could be saved. Other software speaking the same API therefore works, but Pleroma is what this targets.