From d50dd940cf4170bc84dceddd36b9d7194ff907ee Mon Sep 17 00:00:00 2001 From: Kshitij Bachhav Date: Wed, 5 Aug 2026 00:47:28 +0530 Subject: [PATCH 1/2] feed: three targets in a card, not one with buttons inside it Running the app turned up a nested button: the avatar's link to the profile and the like control both sat inside the card's own Pressable. Native tolerates that, the web surface renders invalid DOM, and a screen reader announces the whole row as one control with mystery children everywhere. Now the byline, the body and the like are siblings: tap the name for the author, the title for the thread, the heart to like. Console is clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017DRL4dVgg5oebLSTenQv2G --- apps/mobile/src/components/feed-card.tsx | 69 +++++++++++++----------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/apps/mobile/src/components/feed-card.tsx b/apps/mobile/src/components/feed-card.tsx index 9ab8b6f..88eec68 100644 --- a/apps/mobile/src/components/feed-card.tsx +++ b/apps/mobile/src/components/feed-card.tsx @@ -80,30 +80,29 @@ export function FeedCard({ const name = item.displayName ?? `@${item.handle}`; const completed = event === 'followed-through'; + // Three sibling targets rather than one card with controls nested inside it: + // the byline goes to the author, the body goes to the thread, and the like + // acts in place. Nesting them made a button inside a button -- invalid on the + // web surface, and a screen reader announcing one row as a single control + // with mystery children on every platform. return ( - router.push({ pathname: '/post/[id]', params: { id: item.id } })} + - - - router.push({ pathname: '/profile/[handle]', params: { handle: item.handle } }) - } - className="active:opacity-60" - > - - + + router.push({ pathname: '/profile/[handle]', params: { handle: item.handle } }) + } + className="flex-row items-center gap-2.5 active:opacity-60" + > + @@ -118,22 +117,30 @@ export function FeedCard({ - {completed ? ( - - ) : null} - + {completed ? : null} + - router.push({ pathname: '/post/[id]', params: { id: item.id } })} + className="active:opacity-70" > - {feedTitle(item)} - - {preview.length > 0 ? ( - - {preview} + + {feedTitle(item)} - ) : null} + {preview.length > 0 ? ( + + {preview} + + ) : null} + @@ -146,7 +153,7 @@ export function FeedCard({ ) : null} {item.viewCount > 0 ? : null} - + {!last ? : null} From 557e40726397c973b6b309beaf97141e7884185f Mon Sep 17 00:00:00 2001 From: Kshitij Bachhav Date: Wed, 5 Aug 2026 01:02:23 +0530 Subject: [PATCH 2/2] readme: front door, not a pitch The intro had grown into a sales page: three sections explaining the loop, the sharing and the positioning before anyone reached a command. All of that moves to docs/about.md, which is where somebody who wants the argument can go and find it. What stays is what a README is for -- what this is in four lines, where it has got to, how to run it, and where things live. 99 lines, down from 200 and shorter than before the social layer landed. docs/about.md is the friendly read; product.md stays the authoritative one and says so at the top of it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017DRL4dVgg5oebLSTenQv2G --- README.md | 178 +++++++++++++------------------------------------- docs/about.md | 94 ++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 132 deletions(-) create mode 100644 docs/about.md diff --git a/README.md b/README.md index 0dd2dff..1bed9ac 100644 --- a/README.md +++ b/README.md @@ -6,144 +6,40 @@ Private by default. Works offline. Open source. --- -## What it's for +Write a note. When it's about something you meant to do, it comes back later and asks one +question — *anything come of this?* — with four answers: **Not yet · Add update · Done · Let go.** -You write things down and then you forget them. Not because you're careless — because a note just -sits there. Nothing about it ever asks for your attention again. +No due dates, no streaks, no guilt. -DailyNote is a normal notes app: open it, write, close it. Nothing to set up, no account, no -internet needed. But when a note is about something you actually meant to do, it comes back to you -later and asks one question: +The ones worth showing people, you can publish to a **Feed** — note by note, afterwards, and only +if you want to. Nothing is public unless you say so, and writing needs no account at all. -> **Anything come of this?** - -You get four answers. *Not yet.* *Add update.* *Done.* *Let go.* - -That's the whole idea. Not reminders, not deadlines, not a streak you'll feel bad about breaking. -Just a note that shows up again at a sensible moment and gives you an easy way to say what -happened — including "nothing, and I'm letting it go." - -There are no due dates and no overdue badges anywhere in this app. Feeling guilty is the thing we -are trying to avoid, not the thing we're trying to cause. - -## And if you want, you can share - -Some of what you write turns out to be worth showing people. A thing you said you'd learn, and -then did. A project that went somewhere. - -So there's a **Feed**. You can publish a note you already wrote, or write a post straight to the -feed if that's what you feel like. You can read what other people have shared and follow the ones -worth following. - -Two promises about it: - -- **Your notes are private and stay that way.** Publishing is something you choose, note by note, - afterwards. Nothing you write is public unless you say so. -- **You never have to.** No account is needed to write notes. If you never publish anything, you - haven't missed out on anything — you just have a notes app, which is the point. - -The writing screen never asks who's going to see it. Posting lives on its own tab, behind its own -composer, and tells you it's public before you type a word. - -## What makes it different - -Most feeds are full of people saying what they're *about* to do. "I'm going to learn Rust" is easy -to post and worth nothing. - -Here, the thing that shows up in the feed is a **thread**: the original note, plus what actually -happened to it over time. When one resurfaces six weeks later carrying *"Done — here's what I -built"*, that's something no other feed has. Follow-through is the whole product, pointed inward -first and outward second. - ---- +📖 **[The longer version](docs/about.md)** — the idea, the sharing, accounts, privacy +· 📐 [What it is, precisely](docs/product.md) · 🔑 [Accounts setup](docs/auth.md) ## Status -Being built in the open, by one person, part-time. It runs, and it's already usable for notes. - -**Two things are honest to say up front:** there's no server yet, so the social side runs against -local storage with a few sample authors standing in for other people. And syncing between devices -isn't built, so your notes live on the device that wrote them. - -| | Rollout 1 — the notes app | | -|---|---|---| -| 0 | Foundations | ✅ done | -| 1 | A good local notes app | ✅ mostly there | -| 2 | Capture surfaces — share sheet, URL scheme | — | -| 3 | Follow-through — the differentiator | 🚧 notes come back; the four-answer card is next | -| 4 | Accounts and sync | 🚧 sign-in done, sync not | -| 5 | Import and expansion | — | -| 6 | Ship | — | - -| | Rollout 2 — the social layer ([plan](docs/plan-rollout-2.md)) | | -|---|---|---| -| 2.0 | Identity — profiles and handles | ✅ done | -| 2.1 | Publishing | 🚧 works in the app; public web pages not built | -| 2.2 | Follows and a chronological feed | ✅ done | -| 2.3 | Conversation and safety | 🚧 likes, block and report done; comments not | -| 2.4 | Themes as data, wallpapers | — | -| 2.5 | Discovery | — | - -Full plans: [docs/plan.md](docs/plan.md) · [docs/plan-rollout-2.md](docs/plan-rollout-2.md). -What the product *is*, as opposed to how it gets built: [docs/product.md](docs/product.md). - -## Accounts - -You don't need one to write notes. You never will — that isn't a trial, it's the design. - -An account is asked for at exactly one moment: when you want to put something where other people -can read it. There are three ways in, and they all end up at the same account: - -- **Google** -- **A six-digit code by email** — no password, so there's nothing to forget and nothing to leak -- **Sign in with Apple** — on iOS - -Signing out leaves every note exactly where it is. They were never the account's. - -Setting this up for your own build takes a few console steps: [docs/auth.md](docs/auth.md). - -## Privacy - -**This app does not use end-to-end encryption, and the server can read your notes.** Notes are -encrypted in transit and at rest. Any note can be marked **local-only**, and those never leave your -device: publishing one is refused in code, and the server schema carries a constraint that makes a -published local-only note impossible to store at all. - -That's a deliberate trade: end-to-end encryption would permanently rule out server-side search. The -whole server is open source and self-hostable, which is our substitute for "trust us." See -[ADR 0002](docs/adr/0002-no-end-to-end-encryption.md). - -There's also **no integration with Apple Notes or Google Keep**. Neither has an API to build -against, so it's dropped rather than promised -([ADR 0003](docs/adr/0003-no-notes-app-integrations.md)). - ---- +Built in the open, by one person, part-time. Usable for notes today. -## For developers +Two things to know before cloning: **there's no server yet**, so the social side runs against local +storage with a few sample authors standing in for other people, and **there's no sync**, so notes +live on the device that wrote them. -### Layout +**[Rollout 1 — the notes app](docs/plan.md)** -``` -apps/mobile Expo app (expo-router, NativeWind) MIT -packages/core pure TS domain logic, no RN, no Supabase MIT -packages/db Drizzle schema + Postgres migrations AGPL-3.0 -supabase migrations, RLS policies AGPL-3.0 -docs product.md, the rollout plans, auth.md, ADRs -``` +| ✅ | Foundations · a good local notes app | +|---|---| +| 🚧 | Follow-through (notes come back; the four-answer card is next) · accounts (sync is next) | +| — | Share sheet and URL scheme · import · ship | -`packages/core` holds hashtag parsing, title derivation, the review scheduler, search ranking and -feed composition. These are the pieces most likely to have subtle bugs and the most annoying to -debug through a UI, so they're pure functions with unit tests and no simulator involved. +**[Rollout 2 — the social layer](docs/plan-rollout-2.md)** -Two seams worth knowing about: +| ✅ | Profiles and handles · follows and the feed | +|---|---| +| 🚧 | Publishing (works in-app; public web pages not built) · safety (no comments yet) | +| — | Themes and wallpapers · discovery | -- **`apps/mobile/src/db/social-repo.ts`** is the entire social storage layer. When there's a real - server, that one file gets reimplemented over HTTP and nothing else changes. -- **Feed rules are code, not database policy.** Row-level security answers one question — "is this - row public" — and following, blocking and ordering are decided in `packages/core/src/social.ts`, - where they can be tested. - -### Getting started +## Getting started ```bash npm install @@ -151,9 +47,8 @@ npm start # Expo dev server npm run android # or: npm run ios ``` -Requires Node ≥ 20.19. - -You'll need a **development build** rather than Expo Go — Google Sign-In is native code: +Requires Node ≥ 20.19, and a **development build** rather than Expo Go — Google Sign-In is native +code: ```bash npx expo prebuild --clean @@ -161,8 +56,7 @@ npx expo run:android ``` Sign-in needs credentials. Without them the app still launches, still takes notes and still reads -the feed; it just can't sign anyone in. Copy `apps/mobile/.env.example` to `.env.local` and see -[docs/auth.md](docs/auth.md). +the feed. Copy `apps/mobile/.env.example` to `.env.local` and see [docs/auth.md](docs/auth.md). ### Checks @@ -174,7 +68,27 @@ npm run lint All three run in CI on every PR. -### Database +## Layout + +``` +apps/mobile Expo app (expo-router, NativeWind) MIT +packages/core pure TS domain logic, no RN, no Supabase MIT +packages/db Drizzle schema + Postgres migrations AGPL-3.0 +supabase migrations, RLS policies AGPL-3.0 +docs about.md, product.md, the plans, auth.md, ADRs +``` + +`packages/core` holds hashtag parsing, title derivation, the review scheduler, search ranking and +feed composition — the pieces most likely to have subtle bugs and the most annoying to debug +through a UI, so they're pure functions with unit tests and no simulator involved. + +Two seams worth knowing about: + +- **`apps/mobile/src/db/social-repo.ts`** is the entire social storage layer. When there's a real + server, that one file gets reimplemented over HTTP and nothing else changes. +- **Feed rules are code, not database policy.** Row-level security answers one question — "is this + row public" — while following, blocking and ordering are decided in + `packages/core/src/social.ts`, where they can be tested. The app is local-only until sync lands, so nothing here touches Postgres day to day. To work on the schema, see [supabase/README.md](supabase/README.md). diff --git a/docs/about.md b/docs/about.md new file mode 100644 index 0000000..1652da9 --- /dev/null +++ b/docs/about.md @@ -0,0 +1,94 @@ +# The longer version + +The friendly explanation. For what the product *is* as a set of decisions — +what's in, what's out, and which rules are not up for renegotiation — see +[product.md](product.md), which is the authoritative one. + +## Why a note that comes back + +You write things down and then you forget them. Not because you're careless — because a note just +sits there. Nothing about it ever asks for your attention again. + +DailyNote is a normal notes app: open it, write, close it. Nothing to set up, no account, no +internet needed. But when a note is about something you actually meant to do, it comes back to you +later and asks one question: + +> **Anything come of this?** + +You get four answers. *Not yet.* *Add update.* *Done.* *Let go.* + +That's the whole idea. Not reminders, not deadlines, not a streak you'll feel bad about breaking. +Just a note that shows up again at a sensible moment and gives you an easy way to say what +happened — including "nothing, and I'm letting it go." + +There are no due dates and no overdue badges anywhere in this app. Feeling guilty is the thing we +are trying to avoid, not the thing we're trying to cause. + +The intervals are a loose ladder — **+2d → +7d → +21d → +60d** — and answering *Not yet* pushes it +along. Tags are inline `#hashtags` parsed out of what you wrote; there's no tag picker. Writing +`#do` promotes a note to a commitment, the same as tapping **Follow up**. + +## The sharing part + +Some of what you write turns out to be worth showing people. A thing you said you'd learn, and +then did. A project that went somewhere. + +So there's a **Feed**. You can publish a note you already wrote, or write a post straight to the +feed if that's what you feel like. You can read what other people have shared, follow the ones +worth following, and block or report the ones that aren't. + +Two promises about it: + +- **Your notes are private and stay that way.** Publishing is something you choose, note by note, + afterwards. Nothing you write is public unless you say so. +- **You never have to.** No account is needed to write notes. If you never publish anything, you + haven't missed out on anything — you just have a notes app, which is the point. + +The writing screen never asks who's going to see it. Posting lives on its own tab, behind its own +composer, and tells you it's public before you type a word. That separation is structural, not a +matter of taste — see rules 1 to 3 in [product.md](product.md). + +## What makes it different + +Most feeds are full of people saying what they're *about* to do. "I'm going to learn Rust" is easy +to post and worth nothing. + +Here, the thing that appears in the feed is a **thread**: the original note, plus what actually +happened to it over time. Feed order is by last activity rather than by creation date, so a thread +resurfacing six weeks later carrying *"Done — here's what I built"* comes back to the top. That's +something no other feed has, and it's why threads were built before publishing was. + +Follow-through is the whole product, pointed inward first and outward second. + +## Accounts + +You don't need one to write notes, and you never will — that isn't a trial tier, it's the design. + +An account is asked for at exactly one moment: when you want to put something where other people +can read it. Three ways in, all landing on the same account: + +- **Google** +- **A six-digit code by email** — no password, so there's nothing to forget and nothing to leak +- **Sign in with Apple** — on iOS + +Your handle is separate from how you signed in, and is never prefilled from it. Pseudonymous +accounts are allowed on purpose. Signing out leaves every note exactly where it is; they were +never the account's. + +Setting this up for your own build: [auth.md](auth.md). + +## Privacy, and the trade behind it + +**This app does not use end-to-end encryption, and the server can read your notes.** Notes are +encrypted in transit and at rest. Any note can be marked **local-only**, and those never leave your +device: publishing one is refused in code, and the server schema carries a constraint that makes a +published local-only note impossible to store at all. + +That's deliberate. End-to-end encryption would permanently rule out server-side search, which is a +feature worth more here than a guarantee we'd then have to work around. The whole server is open +source and self-hostable, which is our substitute for "trust us." The reasoning is written up in +[ADR 0002](adr/0002-no-end-to-end-encryption.md). + +There's also **no integration with Apple Notes or Google Keep**. Neither has an API to build +against, so it's dropped rather than promised ([ADR 0003](adr/0003-no-notes-app-integrations.md)). +Capture is the composer, the share sheet, and a `dailynote://` link.