Planet4.Me docs
Open the app

#Backlog

Open requests, captured as they were reported. This file is neither ARCHITECTURE.md (what exists, present tense) nor the product spec (what is intended and designed). It is a holding pen: unsorted asks, unanswered questions, and observed bugs, kept in the words they were reported in so the original intent survives the trip to whoever picks one up.

Nothing in the open sections is designed yet. An item that graduates gets designed, built, and written up in ARCHITECTURE.md — and then moves to Done at the foot of this file, in the words it was reported in, with one line saying where it landed. It used to be struck outright in the same commit, which loses the only record that the ask was ever made: the same request comes back a year later and somebody spends an afternoon establishing that the app already does it. ARCHITECTURE.md says what exists; this says what was asked for and answered.

Status marks: ? open question, not yet a request. done, under Done.

#People

  • ? Let me see options for search by related content — a way from a person, or from what was found by name, to everything connected to it. Two of the options are built, under Done (shared facts and co-mentioned people, on the person's page). The rest, none chosen:

    • Shared tags: the notes and to-dos that carry the same #tag as the match (/tags already indexes them; this would be the crossing from a person's page to the tags their appearances wear).
    • Linked to-dos and questions: the notes a to-do was captured from and the to-dos and questions a note captured (todoSources / todoChips and the question pins already resolve both directions).
    • Same day: notes and to-dos whose pinned dates fall on the same day as the match (the calendar already reads those).
    • Shared words: rank other notes and to-dos by the distinctive words they share with the match — plain statistics, no model, though trigramSimilarity is the wrong tool (it dilutes over a body).
    • Ask the model: a seam in src/data/intelligence.ts returning related entries for a note, null without App Check like every other seam.

#Performance

The app has no indexer: every client subscribes to the whole household's notes, tasks, questions and people, holds them in memory, and derives everything at render time — search re-scans every document's text on each keystroke, and tags, mentions, facts and row counts are recomputed from the text on every render. Linear in content, and cheap at household scale (comfortably into the low thousands of documents). Staged fixes for when that stops being true, cheapest first; none is warranted yet, and each keeps the text as the single truth for as long as it can.

  • Memoize each document's derived facts — folded text, tags, mentions, facts, dates — keyed by the document's id and updatedAt, so a keystroke is a string search over cached folds rather than a rescan, and a row's counts are computed once per edit rather than once per render. matchesSearch also rebuilds the people fold map per document; build it once per query.
  • Debounce the search input (a keystroke or two), so typing a word costs one scan rather than one per letter.
  • Keep an in-memory inverted index — tag, person id and folded word to document ids — updated incrementally from snapshot changes (the listener already delivers per-document diffs), so a query is a set intersection.
  • Only beyond that, server help: store tags and mention ids on documents at write time so Firestore can query them (array-contains, paginated), or a Cloud Function maintaining a search index per household. This is the one step that stores derived data, and the last to take.
  • The first ceiling is the subscription model, not the search: tens of thousands of documents mean slow first paint, memory pressure and a heavy IndexedDB cache whatever the matching costs. Paginated or windowed subscriptions (archived notes on demand, completed to-dos never loaded) come before any of the above if a household ever gets there. The first one is taken: checked items, the set that only ever grows, are windowed to the household's trips look-back (lookBack on the household document, ninety days by default, set in Settings). The other four surfaces the setting names — calendar, people and tags, questions, to-do links and the assistant — are windowed as DERIVATIONS only: their listeners still load every note, to-do and question whole, and that is the next ceiling. Completed to-dos and archived notes are the next two, and both still load whole.

#Notes and photos

#To-dos and lists

  • Ability to reorder notes. The to-dos half is done, under Done; notes still sort by when they were last written. (Moving either between groups is done — hold a row; groups themselves sort by name and have no order field, see DECISIONS, "Groups nest; the URL does not".)

#The assistant

  • The LLM should be able to add images to notes.

#Households

  • #family in the search grammar resolving to the first shelf of a household called Family, as #personal does: tagGroup names shelves by their stored word today.

#Sync, storage, platform

  • Better local storage support, for speed and for offline.
  • In Safari, the app continually reloads/refreshes.

#Admin

  • Add an admin panel and an error log. Reported as one ask; whether the log belongs inside the panel is not settled, and neither is who counts as an admin — firestore.rules opens every door on household membership alone (the assistant's own thread aside), so there is nobody a panel could be restricted to yet.

#Tests

  • smoke.mjs's purchaseCount sums purchases across EVERY household in the emulator and asserts exact equality, so it fails once the emulator has accumulated households from repeated runs: each poll costs one HTTP fetch per household, and waitForPurchases' 40 × 250 ms stops being enough. Observed after ~15 suite runs against one long-lived emulator — it passed again immediately on a fresh one, twice. Scoping the count to the household the run just created would fix it; the global sum was never the claim.

#Integrations

  • ? Outlook integration?

#Done

Asks that are answered by the app as it stands. Each is here in the words it was reported in, with where it landed — enough to check the claim without reading the diff.

  • Are some of the UI colors green-tinted even when the accent color is some other color? They were: the ground, surfaces, borders and text greys were sage in every workspace, and only the accent followed the tone. Each tone now casts the neutrals toward its own hue by the same faint amount (tokens.css, per data-ws), light and dark; emerald keeps the sage. See DECISIONS, "A workspace's colour is yours".
  • Change the login screen copy. The signed-out screen says what the app is in the words given: "A productivity app for scatterbrained people", the three paragraphs under it, and the PWA manifest's description matches the tagline.
  • Pull the Look back section into its own Settings card. A section of its own under Workspace, always open, headed Look back, with the same description and fields the shelf held.
  • Add a clear field button in the search fields. Like a small x on the right side of the field, inside. Every search box — the header's bar on every list screen and the merge card's — is SearchField: a small ✕ inside the field at the right, shown only while there is text, clearing it in one tap and keeping the focus (and the bar) where it was. The browser's own clear is hidden so there is one ✕ everywhere. Found on the way and fixed: typing into a search bar faster than the router re-rendered dropped keystrokes, since the field was controlled from the URL; it now holds its own draft and reads the URL as an acknowledgement.
  • "Work" as a root folder doesn't make much sense in a shared workspace. It is no longer a shelf there: a shared workspace's doors, filter tree, move sheet and parent picker show Personal (wearing the workspace's name) and the household's own groups, with no Work — unless something is already filed under it, in which case it stays, counted, until that is moved out (builtinShelves, on the work flag builtinNamesFor sets). A solo workspace is untouched. See DECISIONS, "A shared household has no Work shelf".
  • Let me search people by job or nickname. The People search — and the merge card's roster, which is the same search — answers to a person's nickname and to part of their job as well as their name and folded-away aliases (searchPeople), so "plumb" finds every plumber the household wrote down. The box says so: "Search people by name or job".
  • Let me see options for search by related content — a way from a person, or from what was found by name, to everything connected to it. Two crossings, both on the person's page, under the rows: "Named alongside" — co-mentioned people, whoever else the same texts name in two of them or more, most often first — and "Same details elsewhere" — shared facts, every number, email, link or address from the person's texts that other texts carry, each over the texts that carry it. Derived from the live texts, nothing stored. The other five options stay open above. See ARCHITECTURE, src/lib/people/ and the person page; DECISIONS, "A person's page reaches sideways through what it already reads"; person-related.mjs.
  • Ability to reorder to-do items and notes. The to-dos: on a group's screen, hold a row and the menu offers Move up, Move down and Move to another group; the order is a key on the to-do, one write per move after the first, and a group nobody reordered reads as it always did. Notes are still the open item above. See ARCHITECTURE, "A group's rows are in the household's order", DECISIONS, "Order is a key between neighbours", and task-reorder.mjs.
  • show nicknames next to person names in People list Beside the name in the muted voice, on the directory row and on the merge card's roster — the screen that asks whether two people are one is the screen that most needs the field telling them apart. One .nick class now, shared with the @ menu and the search's offers.
  • People merge always happens backwards to how I expect it to work. I want to be able to reverse the merge direction before confirming. The card has a "Keep this name" row: this page's person by default, any of the people involved instead. The consequence sentence and the button both read in the chosen direction, and keeping the other name walks you to their page. See DECISIONS, "The survivor is a choice, defaulting to where you stand", and people-merge.mjs.
  • Move UI is a fuckin mess. Asked what about it: the flat list hid the tree, and the sheet did not account for the screen size — it went off the screen and did not scroll. It is the filter's sheet now: the same capped, scrolling Popup, the same tree rows, children indented under their parents, and the group the thing is already on ticked and inert instead of missing. See DECISIONS, "The move sheet is the filter's sheet", and item-move.mjs, which files a to-do into an indented subgroup after scrolling to it.
  • Removing a photo from a note makes it difficult to exit the lightbox The viewer closed on a count, not a position: removing the last of several left the carousel on an index the shrunken row no longer had, which drew a blank slide with no footer on it — no Remove, no Read text, and nothing that looked like a way out. It tracks the live index now and walks to the neighbour, closing only when the last photo goes. note-photos.mjs covers all three cases.
  • need a way to edit or remove detected text on a photo attachment on a note "Edit text" and "Clear text", beside "Add to note" in the photo's own footer. A correction is written to the receipt like any reading, so every device sees it; clearing writes null, which is also what brings "Read text" back for another go. See DECISIONS, "Transcription is a button, not a default", which now says the cache is a person's to invalidate.
  • "Photo didn't take" error needs a way to resolve. Have a 16mb image that I wanted to attach but couldn't. What are my options? The options are now: attach it. The size was never the problem — the client already recompressed under the ceiling — but the decode in front of that was, since createImageBitmap decodes at full size and a screenshot's worth of megapixels is hundreds of megabytes of RGBA on a phone. The file's own header is read first and the decoder asked to scale as it decodes; photo-large.mjs attaches a 50 MB PNG and checks what lands. And when something does fail, the toast names which failure it was and offers "Try again" with the file still in hand. See DECISIONS, "A big photo is scaled on the way in".
  • Need a flat view of Notes like exists with tasks /notes/all, reached from a quiet row on the Notes door with the count on it — the to-dos' own link, on the door that renders the same cards. Ordinary note rows, each wearing its group as a chip that goes there, with the same search, group filter and sort in the query string. selectNotes gained the only set selectTasks already had. See ARCHITECTURE, "Notes", and DECISIONS, "The flat view is a route", plus all-notes.mjs.
  • Adding tasks without connectivity didn't work, but failed silently. Asked what was seen: the row never appeared. That half did not reproduce — offline-add.mjs adds with Firestore refused, hung, and in airplane mode, and the row appears in tens of milliseconds under each, survives a reload, and lands when the path returns. A phone on a build older than the cache-first boot (862ae09) had no list to add TO while offline, which reads the same from outside. The silent half was real and is closed: a queued write the server later refuses now names itself on the connection strip ("a to-do wasn't saved") and in Settings › Service, and the offline chip counts what is waiting. See DECISIONS, "A swallowed failure is still written down".
  • Add Planet4 capabilities document to LLM context so I can ask it questions about how the app works. functions/src/capabilities.ts, a plain-prose description of every screen and rule, appended to the assistant's system prompt and cached. The prompt tells it to answer app questions from the document and to say when the document does not cover one. Held honest by a test against the blank note's cheat sheet and by CLAUDE.md's same-commit rule. Needs a functions deploy. See DECISIONS, "The assistant is told what the app is".
  • LLM assistant still flaky and doesn't always respond or responds with garbled text. Not enough visibility on the client into what the LLM is doing or why. Generic "Searching the Web" message isn't helpful and hides what it's actually doing. I want more detail. The reply is prose now and the actions come back through a strict tool call, which takes free text out of the grammar mask the garbling was traced to. The drawer keeps a log of what the model does — each search named, each note read or missed, its actions arriving, the budget closing — for the turn and behind a disclosure after it; a failed turn's question wears which failure it was and a Retry; an empty reply is refused rather than stored; and a question nothing answered is kept out of the next turn's history. Needs a functions deploy. See DECISIONS, "The reply is prose; the actions are a tool call".
  • LLM output is sometimes garbled — dropped and duplicated character runs inside words. Reported samples: "Fir enough!" for "Fair enough!", "a general definGil definition", "7-Elevenven"; later a stored reply of ":". The analysis that ruled out the transport and pointed at the grammar mask over free prose stands; the reply no longer goes through one. The persisted-message check the item asked for was answered by the screenshot (":" was the persisted message). Same commit as the item above.
  • Can't scroll detected text in note attachments. Might be solved IDK. It is: a photo's transcript is capped at 30dvh and scrolls, with the finger given pan-y over it and the viewer's wheel-to-zoom relabelling exempting the footer, so a mouse scrolls the words rather than zooming the photo. note-photos.mjs asserts both. A phone still seeing it is running a build older than that change.
  • Please hide people search by default behind a status bar button/icon. And the tag filter section needs to be condensed into a multi-level tree dropdown, so please hide that behind a filter icon button as well. It probably makes sense to turn this into a component that can be reused across multiple views: People, Tags, Questions, Tasks, Notes, maybe Calendar. ToolHeader: Search and Filter icons in the header's right slot on every list screen; the search bar slides in under the header, the filter is a bottom sheet with the two-level group tree and the sort. Tags went into the search as #tag tokens (direction C), with completions. See ARCHITECTURE, "Search", and DECISIONS, "Search and filter live behind the header".
  • Bring back the calendar title bar and add the filter there too. The calendar renders ToolHeader with the group filter sheet; the chip strip under the month is gone, the month bar stays.
  • On Task read-only mode list items, can you shorten hyperlinks? I also mean in the detail text appended to Tasks. In edit mode I need the full link of course, but not in preview/read mode. At rest a link's chip reads only its host (shortFactLabel) and the detail line folds every link to its host with a /… for a path (shortenLinks); the editor and the stored text keep the whole link.
  • People need a nickname field that I can fill in, and that can be autocompleted in-line. A nickname on the person document, set on the person's page ("Add a nickname" / Change); one more fold the person answers to everywhere a handle resolves, and the word the @ menu and the search's @ completion splice when there is one. See DECISIONS, "A nickname is an alias somebody meant".
  • Answer mode in questions section doesn't stretch to full width. The row's column aligned its children to the start, leaving the input at its intrinsic width; it now takes the row.
  • Some of the info chips are a little off-center and look a little wonky (Questions). An inline anchor wearing block padding sat off its line; the compact row chip is inline-flex and centred, and the chip row aligns its items. See DECISIONS, "A chip inside a row is a label".
  • The chips in list items (vs in the notes info chip row) should have their own chip design that is more compact. .chip.compact, one size down and no tap height, on every chip inside a to-do, question or note row.
  • The hyperlink chips can be shorter on task items. They should probably also have the same design as the info chips. The to-do strip's facts, dates, tags, group and "from" links wear the compact row chip instead of the inline-verb style.
  • 'Answer' on question list items should probably have a different design now. It keeps the app's inline verb, flush with the question's text — the row's one accent word now that its chips are quiet pills.
  • Notes list items should have the attachment count on them. Same icons but without the badge or the chevron. The title line carries the shelf line's icons and counts — photos, facts, dates, to-dos, questions, people — via noteCarries; the photo count is a new photoCount read cache on the note document. See ARCHITECTURE, "A note's row states what it carries".
  • I want a unified search in notes and tasks groups that searches anything at the same level or deeper, and that returns documents or folders as results. Also since it's unified move it to the top of the page. Also move new group and new document controls to the top. One LevelSearch box at the top of both doors and every group screen; it searches the level and its subtree, never up, and answers with matching subgroups as cards and matching notes or to-dos as rows (ways in, from the door). New group and the to-do capture moved above the cards; the notes' New stays in the header. See ARCHITECTURE, "Search", and DECISIONS, "Search looks down, never up".
  • Group area in Notes and Tasks is too big. Make it one container and shrink the fonts a little. A level's group cards now sit inside one raised container as rows (.group-door), hairlines between, title and count one size down from a list card. Both doors and every group screen, since they render the same GroupCards.
  • I need a people search by name. A search box at the top of the People directory (/people), over names and the aliases a rename or merge folded away (searchPeople, the merge card's own search), ?q= in the query string beside the group filter.
  • Allow subfolders in Notes. Groups nest, three deep counting documents, on both doors at once: one parent field on the one document both surfaces read. Hold a card and Move it. See ARCHITECTURE, "Groups nest, and the door renders it as an indent", and DECISIONS, "Groups nest; the URL does not". The door also folds a parent's children away — chevron at the card's left edge, with what it holds said on the card.
  • Group creation should autocomplete existing group names from other sections. As chips rather than as autocomplete, and deliberately: a name can no longer find a document, since a group can be renamed out from under one. Each chip under the New group box carries its group's id and joins THAT document (joinableGroups), narrowed by what is typed with the same fold search uses. Typing a name the other door already has is refused and pointed at the chip. See DECISIONS, "A group door offers what the other door already has".
  • Todo Info chips are still not interactable. They are links now, on the row's own second line outside the label button: FactLinks for phone numbers, addresses and URLs, DateChips to the day on the calendar, and "from note" back to the note a to-do was captured from. The strip outlives the edit state, so a chip pressed mid-edit is still under the finger when the click lands. See DECISIONS, "The row's chips live on the row, not only inside its editor".
  • Add CTRL+Enter or CMD+Enter to submit a todo item or list item edit. In the to-do editor, which is multiline — Enter is a line break there and the save is the blur, so the chord is the keyboard's own done (TasksScreen.tsx). A list item's rename is single-line and has always committed on plain Enter; there is nothing for the chord to add.
  • Allow the LLM to edit notes when on the note page. edit_note, available only while the snapshot says a note is open, and only for that note. Every other destructive verb stays out of the drawer. See DECISIONS, "The assistant may rewrite the note in front of you, and nothing else".
  • LLM should have access to note's attached photos when viewing a note. That note's photos ride along as images on the message, and the prompt tells the model they are part of the note and never to claim to see one that is not attached.
  • Add ability to copy/paste image data from clipboard onto a note. The note page takes a paste anywhere on it and files the image as a note photo.
  • Allow zoom/pan on photos added to notes. Full-size review swipes between a note's photos and pinch-zooms, with each photo's own controls in the footer — read its text, put that text in the note, remove it.
  • Tapping a photo on a note takes you to the photos section, not necessarily the specific photo tapped. It opens at the photo tapped: the thumbnail hands the viewer its index.
  • Add AI/LLM icon to Notes' "Read Text" button on photos/images. AiMark, the one mark every control that calls the model wears.
  • Remove "Archive" label from note detail screen. Or make it an icon. An icon, like its camera neighbour — the word crowded the title beside it. The label survives as the accessible name and the hover title.
  • Make the "can't sync" error button a retry or something. It is a button: tapping it opens the explanation it used to hoard in a hover title, plus the one honest action, a reload. Retrying continues by itself either way; the button is for whoever wants it settled now.
  • Pull questions out of notes? Notes already capture typed to-dos; a question is a different kind of line and it is not settled that it should become anything. It became something, and the settling is what shaped it: a question is nearer a note than a to-do, because a task is destroyed by completing it while a question is completed by being answered and then kept. ask @sam: … on a body line is captured at save through the to-dos' own machinery, pinned (now a question), and answering stamps the answer back into the line so the note stays self-contained. It surfaces on the note, its group's shelf, the person it is for, and /questions behind More — never badged. See ARCHITECTURE, src/lib/questions/ and the questions collection, and DECISIONS, "A question is answered by a person, and then kept".

#Answered

Questions that needed an answer, not a change. Done above records what shipped; an answered question records its answer, which is the whole of what it left behind.

  • Does removing a photo from a note orphan it in Storage? It deletes it. deleteNotePhoto removes the receipt document first, then the Storage object; a permanent note delete cascades over every photo the same way (deleteAllNotePhotos), and storage.rules allows the delete. The only orphan windows are deliberate: an upload that dies between bytes and receipt, or an object delete that fails after its receipt is gone, both leave invisible garbage — documented in src/data/notePhotos.ts as the cheaper failure, since the reverse order shows someone a broken image forever.