Skip to content

Max ​

Max is rule-based. Everything Max says is computed from the plan, its attendees and its items: there are no AI calls and no tables of its own.

Two rules shape the code:

  • Max drafts and proposes. The host sends and approves.
  • Max only says things that are true of the plan as it is. Nothing is invented.

The logic: lib/max/ ​

These are pure functions, with no database or browser access, so they are easy to test.

FileWhat it does
insights.tsgetMaxInsights decides what Max has to say to a given viewer
split.tsproposeSplit suggests who brings each free item
messages.tsBuilds the invite, the recap, the nudge and the "who can bring this?" text
activity.tsLists what happened on the plan, from the rows' own timestamps
parse-plan.tsReads a title, date, time and place out of one sentence
plan.ts, types.tsThe slice of a plan Max works with

Insights ​

An insight is one thing Max has to say, with its actions. getMaxInsights returns them for one viewer:

RuleWhoWhen
answerAnyoneThe viewer has not answered, and someone else has
takeA guestThe viewer is in, brings nothing, and an item is free
shareThe hostNo guest has answered yet
nudgeThe hostA guest answered "maybe"
splitThe hostItems are free and someone is in. It needs the host's OK
askThe hostItems are free and nobody is in

Each insight's id includes a fingerprint of the situation, for example the ids of the free items. A viewer can dismiss an insight, and it comes back only when its id changes. Dismissals are kept in the browser, under duplan.max.dismissed.<slug>.

To add a rule, add it to getMaxInsights, give its message and action labels a key under max in the three message files, handle any new action in useMax, and cover it in tests/unit/max.test.ts.

The split ​

proposeSplit gives each free item to the attendee who is coming and carries the least so far. Ties go to whoever answered first. Approving it calls assignItem once per item: the same host-only action as assigning by hand, so the server enforces who may do it.

Reading a sentence ​

parsePlanSentence(text, locale, now) works with regular expressions on a lower-cased, accent-stripped copy of the sentence. It finds a date, then a time, then a place, and takes the start of the first sentence as the title. Word lists exist for English, French and Spanish.

It is best effort. The create flow always shows the result and lets the host correct it, and the function can be replaced without touching the UI.

The UI: components/max/ and lib/hooks/useMax.ts ​

useMax turns the logic into what a screen needs: the insights that have not been dismissed, the activity list, and what each action does (open the answer sheet, take an item, share a drafted text, open the split).

ComponentWhere it shows
MaxCardInline in the plan, next to the section it is about. For everyone
MaxFabThe round M button and its menu, on a phone. Host only
MaxEdgeTabThe tab and side panel, on a tablet. Host only
MaxPanelThe docked column on a wide screen, and the content of the tablet panel. Host only
MaxQueue, MaxSplitDecisionThe approval queue and the split decision, at /p/[slug]/max
MaxInviteCardOn the "plan created" screen

Which of these shows at a given width is decided in CSS, not in JavaScript.

Max's surfaces render only in the browser, after the device's identity and dismissals are known. That keeps the server's HTML and the first client render identical.