WallpaperMachine
The wallpapers you love. A new home on your Mac.
Plans, anniversaries and day counts for the two of you, with a notification when your partner changes something.
LifestyleJavaScript · HTMLGitHub @jowon-kimOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
TT — an open-source couples' calendar you host yourself: your plans stay in your own database. Vanilla JS PWA, Postgres, encrypted Web Push, 9 languages (MIT)
Taken from the repository README
Loading the full README.
Read the full README ↗English · 한국어
An open-source shared calendar for two people that you run yourself — so your plans stay in your own database, not on a couple-app company's server. Each of you signs in, one invites the other, and from then on you share a calendar. When one of you adds something, it shows up on the other's lock screen, and on the morning of the day it speaks up again. No KakaoTalk, no Slack — the app sends the push itself.
| In one line | A couples' calendar you host yourself in 15 minutes, for free — your data in your own Postgres. Plain JavaScript, no framework, no build step |
| Live | tt-calendar.vercel.app is the author's own copy · what it is · days-together calculator |
| Demo | · mp4 — 25 s, a demo couple on the live service |
| Status | Open source (MIT). Runs on the web as a PWA; the Capacitor store shells build in CI but are not published. Run your own: no-code guide · for coding agents · keeping it running |
| Stack | Vanilla JS · Vercel serverless (12 functions max, enforced by a test) · Neon Postgres · Vercel Blob · Web Push / FCM / APNs · Capacitor |
| Tests | 750, npm test (the real handlers against an in-memory Postgres, the screen, i18n, holidays, service worker, public pages) |
| Cost | $0 a month (free tiers) |
| Read next | How it works · Why it is built this way |
public/ is served as-is@neondatabase/serverless, web-push,
@vercel/blob. Android (FCM) and iPhone (APNs) notifications are plain fetch
and Node's http2, not SDKscrypto; native account
chooser in the Android app), invite links and account deletion. Every query is scoped to the
couple, and a two-couple test knocks on every endpoint to prove itandroid/, ios/) with native
notifications, built in CI without a Mac (docs/APPS.md)api/together.jslanding.mjs
with FAQ structured data, hreflang, a sitemap and llms.txtAbout 15,000 lines across api/, lib/ and public/ (nine language packs
included), with 448 integration tests that run the real handlers against an
in-memory Postgres, 226 that run the screen itself, 58 for i18n and holidays,
and 18 for the service worker.
Most couple apps keep your diary, photos and plans on the company's servers. TT is meant to be deployed by the two of you (no-code guide, about 15 minutes, free):
tiles.openfreemap.org (OpenStreetMap data) — they see which area you are looking at, never your
pins or locations, which are drawn in your browseraes128gcm), so the push service relays
them without being able to read them. (The optional store apps use FCM and
APNs, which do not encrypt this way — they are not published, and the web app
doesn't need them)test.mjs runs two
couples against every endpoint to prove nothing leaks between them — read itThe hosting companies (Vercel, Neon) still run the machines, as with any cloud app; the difference is that the data sits in an account you own, not in a service that holds everyone's.
flowchart LR
U[Browser / PWA] --> V[Vercel: static public/ + serverless api/]
A[Store app · Capacitor] -->|loads the same site| V
V --> N[(Neon Postgres)]
V --> B[(Vercel Blob · photos)]
C[cron-job.org · every 30 min] --> V
V --> P[Web Push · FCM · APNs]
G[GitHub Actions] -->|tests · nightly encrypted backup · app builds| V
| Piece | What it does here | Where |
|---|---|---|
| Vercel | Serves public/ as-is and runs api/ as serverless functions. One deployment for every couple; pushing to main deploys | vercel.json, api/ |
| Neon Postgres | Eighteen tables, every calendar row keyed by couple_id. Each preview deployment gets its own database branch, so testing never touches real calendars | schema.sql, lib/store.js |
| Vercel Blob | Calendar photos, private; the database keeps only where each one is | lib/blob.js |
| Outside clock | Knocks on /api/cron every 30 minutes for "starting soon", the 8am summary and the 8pm question of the day, read in each calendar's own time zone | api/cron.js, lib/remind.js |
| Encrypted backup | GitHub Actions dumps the database every night and encrypts it with GPG before keeping it for 30 days — the repo is public, so the artifact has to be useless without the passphrase | .github/workflows/backup.yml |
| PWA | Manifest, a service worker that opens the app with no signal, an install button on Chrome/Edge and picture steps on iPhone | public/sw.js, api/manifest.js |
| Store apps | Capacitor shells around the same site with native notifications. The Android .aab and the iOS TestFlight build come out of CI, no Mac | android/, ios/, .github/workflows/ |
fetch
(or Node's http2 for Apple). Google sign-in checks the ID token against
Google's public keys with crypto. Each SDK skipped is one less thing to
audit and keep currentlib/store.js takes the couple first, and who sits in which seat comes from
the session, never from the request body. A two-couple test knocks on every
endpoint to prove one couple cannot see the other's calendarschema.sql and makes the
VAPID keys, and runs the schema again whenever the file changes. A fresh
deploy needs one database URL and nothing elsenpm test needs no accounts and runs on every push. The screen is tested by
running public/app.js against a stub DOMAGENTS.md lets a coding agent install it
without the authorFollow the step-by-step guide. It's all in the browser — one Deploy button, about 5 minutes. The app makes its own keys and tables the first time it runs. That sets up a copy for the two of you.
Clone the repo, open a CLI coding agent in that folder — Claude Code, Codex, whatever you use — and ask:
how do I use this?
AGENTS.md spells out the setup in a form agents can execute. You
pick a project name; it handles the rest. The only thing you do by hand is one
browser login (vercel login).
Five minutes if you already have the accounts. You don't need to sign up for Neon separately — the Vercel integration creates the account for you.
npm install
npx vercel login
npx vercel link --yes
npx vercel integration add neon # this sets DATABASE_URL
npx vercel env pull .env.local
npm run verify # fix every ✗, read every ⚠
npx vercel deploy --prod
Nothing else is required. The VAPID keys for push are made by the app on its
first request and kept in the database (lib/setup.js), and the tables are
created the same way. Optional extras — a Blob store for photos, a Google
client ID for Google sign-in, CRON_SECRET for the morning clock, and the FCM/APNs keys
for the store apps — are listed in AGENTS.md.
Letting other couples sign up is running a service. Vercel's Hobby plan is
for personal use only, so that needs Vercel Pro, and a paid Neon plan once the
clock runs around the clock (docs/RUNNING.md).
When npm run verify prints a ✗ it also prints the command that fixes it.
Settings → phone notifications → allow. Once per device. Or install the store app, which asks on its own.
On iPhone this only works from a home-screen app. The button does nothing in a Safari tab. Share → "Add to Home Screen" → open it from that icon, then allow. (iOS 16.4+)
Android and desktop Chrome just work.
Events also announce themselves on the day itself: a "starting soon" reminder
30–60 minutes before anything with a time, and one summary at 8am of the day.
Both of you get these. They need one more piece of setup — something has to knock
on /api/cron every 30 minutes (Vercel Cron on Pro, or an outside service). The key is
CRON_SECRET, known only to whoever runs the service.
AGENTS.md walks through it.
The time zone that decides "today" and "8am" lives in each calendar's settings. The app fills it in from your browser the first time you open it.
From the settings screen — app name, greeting, both names, the day you met, theme, calendar photos. No code involved. There is no save button; typing or picking commits it.
Files you can swap
| File | Where it shows up |
|---|---|
public/mascot.png | the big image on the login screen |
public/favicon.png | browser tab · notification icon |
public/badge.png | the status-bar icon on Android |
badge.png has one rule. Android uses only the alpha channel — it paints the
shape with its own color. A color photo turns into a smudge. Export it at 96px,
white on transparent.
Things that need a code change
[data-theme="..."] block in style.css, change the
values, add the id to the THEMES array in app.js, and add a theme.<id>
key to the language packs. The swatch in the picker builds itselfpublic/manifest.default.json and
public/icon-512.png. Each couple can pick their own name and icon in the app
(asked once on first sign-in, then Settings → Home-screen icon); api/manifest.js
serves /manifest.json with their choiceThe app ships in nine languages; Language in settings switches both screens,
since it is a shared setting. Until someone picks one, it follows the browser's
language. Dates, times and weekday names come from
Intl, so they follow the language without anyone writing them down.
A language pack is one file of plain key–value pairs — no library, no build:
public/i18n/en.js the reference pack
public/i18n/ko.js ja.js · zh-CN.js · es.js · fr.js · de.js · pt-BR.js · ru.js
To add one, copy en.js, translate the values, add a <script> for it in
index.html, add the tag to LOCALE_NAMES in i18n/index.js, and import it in
lib/i18n.js so notifications get it too. Anything you
leave out falls back to English rather than showing blank. npm run i18n prints
how complete each pack is.
The server uses the same files, so push notifications arrive in the language the calendar is set to.
Holidays in settings picks a country. Each one is a file that maps dates to name keys:
public/holidays/none.js the default
public/holidays/calc.js Easter and "nth weekday" rules the sets below share
public/holidays/kr.js South Korea, incl. lunar dates and substitute days
public/holidays/jp.js Japan, incl. substitute and citizens' holidays
public/holidays/cn.js mainland China, statutory days (lunar dates to 2036)
public/holidays/us.js US federal holidays, incl. observed days
public/holidays/de.js Germany, nationwide holidays only
public/holidays/fr.js metropolitan France
public/holidays/es.js Spain, national holidays only
public/holidays/br.js Brazil, national holidays
public/holidays/ru.js Russia, the holidays themselves
Each file's header says what it leaves out — regional holidays, and days off
that a government moves by decree each year (China's swapped weekends, Russia's
transfers) cannot be computed. Adding your own country is the most useful
thing you can contribute — copy kr.js, keep the shape (including the
wrapper), register it under a short code, load it in index.html, and add
region.<code> plus the holiday names to the language packs.
Longer-term operational notes — free-tier limits, environment variables you must
not touch, things that rot over time — are in docs/RUNNING.md.
api/ serverless functions (thin handlers)
lib/ store.js calendar SQL · accounts.js account SQL · http.js sessions
push.js · fcm.js · apns.js notifications · blob.js photos
public/ the UI. served as-is, no build
public/i18n/ language packs
public/holidays/ holiday sets, one file per country
public/privacy.html privacy policy (a draft — fill in the blanks)
android/ ios/ Capacitor shells for the store apps
fastlane/ .github/ store builds in CI, no Mac needed
schema.sql eighteen tables — the app runs it itself on first start
landing.mjs builds the public pages (words in landing-content.mjs)
test.mjs 448 integration tests against the real handlers
screen-test.mjs 226 tests that drive public/app.js: sign-in, editor, settings, app push, install
npm test # runs the real handlers against in-memory Postgres. No Neon account needed
npm run verify
Holiday sets and language packs are the most useful things you can send — both are a single self-contained file. See the two sections above.
npm test # handler tests plus the screen, no accounts needed
npm run i18n # how complete each language pack is
MIT — use it, fork it, run it for the two of you. See LICENSE.