Public: anyone can read this; the crew can change it.

The agent can narrow the clock to deals closing this week, this month or later #311

Parked opened this
Closed without merging

mason/deal-clock:mason/309-the-agent-can-narrow-the-clock-to-deals → main 7 commits fork 2 threads · 2 unresolved

  • Closed without merging. Nothing was applied to main.

Review

Sign in to review this change.
32 files changed +224 −27
.bash_profile added
No content changes.
.bashrc added
No content changes.
.claude/agents added
No content changes.
.claude/commands added
No content changes.
.claude/hooks added
No content changes.
.claude/launch.json added
No content changes.
.claude/loop.md added
No content changes.
.claude/output-styles added
No content changes.
.claude/routines added
No content changes.
.claude/scheduled_tasks.json added
No content changes.
.claude/settings.json added
No content changes.
.claude/settings.local.json added
No content changes.
.claude/skills added
No content changes.
.claude/workflows added
No content changes.
.gitconfig added
No content changes.
.gitmodules added
No content changes.
.idea added
No content changes.
.mcp.json added
No content changes.
.profile added
No content changes.
.ripgreprc added
No content changes.
.vscode added
No content changes.
.zprofile added
No content changes.
.zshrc added
No content changes.
README.md +13 −11
@@ -127,17 +127,19 @@ still open. Mark done anyway?", and goes ahead on `yes`.
127127
128128 ## Finding a deal
129129
130 Type part of an address, or of a buyer's, seller's or other party's name,
131 phone or email, into **Find a deal** above the clock and press Find (try
132 `harbor`). The clock, This week and today's reminders narrow to the matches;
133 **Clear** brings them back. The **Side** choice beside it narrows to deals
134 where you are the buyer's agent or the listing agent. 9 Orchard Row is a
135 listing in the samples; on a listing, add "Listing agreement expires" as a
136 date and the clock and the deal page flag a closing that falls after it (9
137 Orchard Row shows the flag). It is yours alone: it is never on the client
138 timeline and no party is drafted anything about it. You see it on the clock, in
139 This week and on the deal's page, and the calendar file gives it an alarm 30
140 days ahead.
130 Type part of an address, or of a buyer's, seller's or other party's name, phone
131 or email, into **Find a deal** above the clock and press Find (try `harbor`).
132 The clock, This week and today's reminders narrow to the matches; **Clear**
133 brings them back. The **Side** choice beside it narrows to deals where you are
134 the buyer's agent or the listing agent. The **Closing** choice (Any, This week,
135 This month, Later) narrows to deals by closing date and composes with Find and
136 Side; This week is today and the six days after, This month runs to the month's
137 end, and an unknown value is refused. 9 Orchard Row is a listing in the
outdated

Re-wrap: this line runs past the paragraph's width.

138 samples; on a listing, add "Listing agreement expires" as a date and the clock
139 and the deal page flag a closing that falls after it (9 Orchard Row shows the
140 flag). It is yours alone: it is never on the client timeline and no party is
141 drafted anything about it. You see it on the clock, in This week and on the
142 deal's page, and the calendar file gives it an alarm 30 days ahead.
141143
142144 ## Opening and shaping a deal
143145
docs/TECHNICAL.md +1
@@ -278,6 +278,7 @@ decide nothing themselves. `AGENTS.md` says how to add to it.
278278 | `src/env.ts` | what the forge told this process |
279279 | `src/coming.ts` | the reminders a deal has yet to draft: a deal, a day and the agent in, up to 10 listed and a count of the rest out, read from `drafts` so it never disagrees; called by the deal page and the terminal |
280280 | `src/find.ts` | finding a deal: `query` validates what was typed (100 characters, no control characters), `find` filters deals by address and party details; called by the clock, routes, MCP and the terminal |
281 | `src/closing-window.ts` | narrowing the clock by closing date: `closingFilter` validates `?closing=` (week, month, later, any), `closingIn` keeps the deals closing in that window from today's date; called by `GET /` and `GET /week` |
281282 | `src/guidance.ts` | the one plain line telling a client what to do for an open date: kind and side in, a string out; called by the client timeline and its page |
282283 | `src/memo.ts` | work done once per deal content: `print` keys a deal by its rows, `memoizer(limit)` is a bounded cache; called by `src/http.ts`, `src/reminders.ts` and the clock and sheet pages |
283284 | `src/money.ts` | the sale price and earnest amount in whole dollars: `amount` validates input, `dollars` formats it one way; called by deals, book, reminders, MCP, routes and the pages |
src/closing-window.test.ts added +117
@@ -0,0 +1,117 @@
1 /** Narrowing the clock by closing window: the buckets, the boundary day, q composing, a bad value refused, and the clock carrying it. */
2 import { afterAll, beforeEach, expect, test } from "bun:test";
3 import { mkdtempSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { CLOSINGS, closingFilter, closingIn } from "./closing-window.ts";
7 import { addDays } from "./dates.ts";
8 import { openDb } from "./db.ts";
9 import { createDeal, listDeals } from "./deals.ts";
10 import { restore } from "./sample.ts";
11 import { find } from "./find.ts";
12 import { forgetRationing } from "./security.ts";
13 import { serve } from "./server.ts";
14 import { sign, type Who } from "./who.ts";
15
16 const on = "2026-11-11"; // a month with 19 days left
17 const at = (days: number) => ({ closing: addDays(on, days) });
18
19 test("each bucket, with the boundary days", () => {
20 const deals = [at(-2), at(0), at(6), at(7), at(19), at(20), at(90)].map((d, i) => ({ id: i, ...d }));
21 const ids = (w: Parameters<typeof closingIn>[2]) => closingIn(deals, on, w).map((d) => d.id);
22 expect(ids("")).toEqual([0, 1, 2, 3, 4, 5, 6]);
23 expect(ids("week")).toEqual([0, 1, 2]); // today + 6 is the last day of the week
24 expect(ids("month")).toEqual([0, 1, 2, 3, 4]); // 2026-11-30 is the last day of the month
25 expect(ids("later")).toEqual([5, 6]);
26 });
27
28 test("a week that runs past the month's end is still inside this month", () => {
29 const deals = [{ closing: "2026-12-03" }, { closing: "2026-12-05" }];
30 expect(closingIn(deals, "2026-11-28", "month")).toEqual([deals[0]!]);
31 expect(closingIn(deals, "2026-11-28", "later")).toEqual([deals[1]!]);
32 });
33
34 test("the value is validated against the list", () => {
35 expect(closingFilter(null)).toBe("");
36 expect(closingFilter("any")).toBe("");
37 for (const c of CLOSINGS) expect(closingFilter(c)).toBe(c);
38 for (const bad of ["Week", "year", "week;", "<b>"]) expect(closingFilter(bad)).toBeUndefined();
outdated

Replace the as never cast with real DealView-shaped values, and add a case over the seeded sample deals dated from today (box 4).

39 });
40
41 const KEY = "a-key-only-the-forge-holds";
42 const dir = mkdtempSync(join(tmpdir(), "app-closing-"));
43 const db = openDb(dir);
44 const alice: Who = { role: "user", id: "u1", name: "alice" };
45 const server = serve({ port: 0, dataDir: dir, identityKey: KEY });
46 const base = `http://${server.hostname}:${server.port}`;
47 afterAll(() => server.stop(true));
48 beforeEach(forgetRationing);
49 const identity = `u1|alice|user|${Date.now() + 600_000}`;
50 const user = { "x-forge-identity": identity, "x-forge-signature": sign(identity, KEY) };
51 const get = (path: string) => fetch(base + path, { headers: user });
52
53 test("the clock and the week sheet narrow, keep the choice, and refuse a bad one", async () => {
54 const today = new Date().toISOString().slice(0, 10);
55 for (const [address, days] of [["1 Soon Way", 2], ["2 Far Way", 400]] as const) {
56 const made = createDeal(db, alice, { address, buyer: "Ann Buyer", seller: "Sam Seller", contractDate: addDays(today, -10), closingDate: addDays(today, days), openAnyway: true });
57 if (!made.ok) throw new Error(made.message);
58 }
59 const week = await (await get("/?closing=week")).text();
60 expect(week).toContain("1 Soon Way");
61 expect(week).not.toContain("2 Far Way");
62 expect(week).toContain('<option value="week" selected>');
63 expect(week).toContain("closing=week");
64 const later = await (await get("/?closing=later&q=far")).text();
65 expect(later).toContain("2 Far Way");
66 expect(later).not.toContain("1 Soon Way");
67 expect(await (await get("/?closing=later&q=soon")).text()).toContain("No deal matches");
68 const bad = await get("/?closing=year");
69 expect(bad.status).toBe(422);
70 expect(await bad.text()).toContain("Closing is any, week, month or later.");
71 expect((await get("/week?closing=year")).status).toBe(422);
72 expect(await (await get("/week?closing=later")).text()).not.toContain("1 Soon Way");
73 });
74
75 const mine = () => {
76 const listed = listDeals(db, alice);
77 if (!listed.ok) throw new Error(listed.message);
78 return listed.value;
79 };
80
81 test("every bucket over the sample deals, dated from today, partitions them", () => {
82 const today = new Date().toISOString().slice(0, 10);
83 restore(db, today);
84 const deals = mine().filter((d) => !d.closed && !d.fell_through);
85 expect(deals.length).toBeGreaterThan(2);
86 const ids = (w: Parameters<typeof closingIn>[2]) => closingIn(deals, today, w).map((d) => d.id).sort();
87 const week = ids("week");
88 const later = ids("later");
89 const month = ids("month");
90 const rest = month.filter((id) => !week.includes(id));
91 expect([...week, ...rest, ...later].sort()).toEqual(ids(""));
92 for (const d of closingIn(deals, today, "week")) expect(d.closing <= addDays(today, 6)).toBe(true);
93 for (const d of closingIn(deals, today, "later")) expect(d.closing > addDays(today, 6)).toBe(true);
94 for (const id of ids("month")) expect(ids("")).toContain(id);
95 expect(week.every((id) => ids("month").includes(id))).toBe(true);
96 });
97
98 test("composes with find on real deals", () => {
99 const today = new Date().toISOString().slice(0, 10);
100 restore(db, today);
101 const deals = mine();
102 const harbor = find(deals, "harbor");
103 expect(harbor.length).toBeGreaterThan(0);
104 const inMonth = closingIn(harbor, today, "month").length;
105 expect(inMonth + closingIn(harbor, today, "later").length).toBe(harbor.length);
106 expect(closingIn(find(deals, "zzz-nothing"), today, "week")).toEqual([]);
107 });
108
109 test("the empty message names what narrowed", async () => {
110 const today = new Date().toISOString().slice(0, 10);
111 db.query("DELETE FROM deal").run();
112 const made = createDeal(db, alice, { address: "4 Lone Way", buyer: "Ann Buyer", seller: "Sam Seller", contractDate: addDays(today, -10), closingDate: addDays(today, 400), openAnyway: true });
113 if (!made.ok) throw new Error(made.message);
114 expect(await (await get("/?closing=week")).text()).toContain("No deal matches that closing.");
115 expect(await (await get("/?closing=week&side=buyer")).text()).toContain("No deal matches those filters.");
116 expect(await (await get("/?closing=week&q=zzz")).text()).toContain("No deal matches “zzz”.");
117 });
src/closing-window.ts added +51
@@ -0,0 +1,51 @@
1 /**
2 * Narrowing the clock by when a deal closes: this week, this month or later.
3 * Plain deals and today's date (in the agent's zone) in, plain deals out.
4 */
5
6 import { addDays } from "./dates.ts";
7
8 export const CLOSINGS = ["week", "month", "later"] as const;
9
10 /** What the clock can be narrowed to; "" is every deal. */
11 export type ClosingFilter = "" | (typeof CLOSINGS)[number];
12
13 /** The choices as the form names them, in order. */
14 export const CLOSING_CHOICES: readonly (readonly [ClosingFilter, string])[] = [
15 ["", "Any"],
16 ["week", "This week"],
17 ["month", "This month"],
18 ["later", "Later"],
19 ];
20
21 export const CLOSING_REFUSAL = "Closing is any, week, month or later.";
22
23 /** `value` as a filter: nothing or "any" is every deal; anything but week, month or later is undefined. */
24 export function closingFilter(value: string | null): ClosingFilter | undefined {
25 if (value === null || value === "" || value === "any") return "";
26 return CLOSINGS.find((c) => c === value);
27 }
28
29 /** The last day a closing counts as "this week": today and the six days after it. */
30 const weekEnd = (on: string): string => addDays(on, 6);
31
32 /** The last day of the calendar month `on` is in, or the week's end when the week runs past it. */
33 function monthEnd(on: string): string {
34 const first = `${on.slice(0, 7)}-01`;
35 const next = addDays(first, 31).slice(0, 7) + "-01";
36 const last = addDays(next, -1);
37 const week = weekEnd(on);
38 return last > week ? last : week;
39 }
40
41 /**
42 * The deals closing in the window. This week runs through six days from today
43 * (a closing already past is still this week's business); This month runs
44 * through the month's end and so includes this week; Later is everything after.
45 */
46 export function closingIn<T extends { readonly closing: string }>(deals: readonly T[], on: string, window: ClosingFilter): T[] {
47 if (window === "") return [...deals];
48 const week = weekEnd(on);
49 const month = monthEnd(on);
50 return deals.filter((d) => (window === "week" ? d.closing <= week : window === "month" ? d.closing <= month : d.closing > month));
51 }
src/pages/clock.ts +17 −11
@@ -18,8 +18,10 @@ import { byDay, lateNote, thisWeek, whereNote } from "../week.ts";
1818 import { layout } from "./layout.ts";
1919 import { mayRestore } from "../actions.ts";
2020 import { badge, draftLink, opens, owedBy, URGENCY_LABEL } from "./sheet.ts";
21 import { type ClosingFilter, closingIn } from "../closing-window.ts";
2122 import { find } from "../find.ts";
2223 import { onSide, type SideFilter } from "../side.ts";
24 import { closingFilterSelect } from "./closing-window.ts";
2325 import { dealSideLabel, sideFilterSelect, sideLabel } from "./side.ts";
2426 import { memoizer, print } from "../memo.ts";
2527 import { bookLine, tally } from "../book.ts";
@@ -102,15 +104,15 @@ export function axis(start: string, days: number, on: string): string {
102104 }
103105
104106 /** The query a link to the week sheet carries to keep the clock's narrowing. */
105 const weekQuery = (q: string, side: SideFilter): string => {
106 const parts = [q === "" ? "" : `q=${encodeURIComponent(q)}`, side === "" ? "" : `side=${side}`].filter((p) => p !== "");
107 const weekQuery = (q: string, side: SideFilter, closing: ClosingFilter): string => {
108 const parts = [q === "" ? "" : `q=${encodeURIComponent(q)}`, side === "" ? "" : `side=${side}`, closing === "" ? "" : `closing=${closing}`].filter((p) => p !== "");
107109 return parts.length === 0 ? "" : `?${parts.join("&")}`;
108110 };
109111
110112 const weekRows = memoizer<string>(3000);
111113
112114 /** Open dates due within the week, under day headings; each row opens its sheet. */
113 export function weekCard(deals: readonly DealView[], on: string, q = "", side: SideFilter = ""): string {
115 export function weekCard(deals: readonly DealView[], on: string, q = "", side: SideFilter = "", closing: ClosingFilter = ""): string {
114116 const groups = byDay(thisWeek(deals, on), on);
115117 const body =
116118 groups.length === 0
@@ -127,7 +129,7 @@ export function weekCard(deals: readonly DealView[], on: string, q = "", side: S
127129 .join("");
128130 return `<section id="week" class="card" data-live="deal.changed">
129131 <h2>This week</h2>
130 <p class="small"><a href="/week${weekQuery(q, side)}">Print this week</a></p>
132 <p class="small"><a href="/week${weekQuery(q, side, closing)}">Print this week</a></p>
131133 ${body}
132134 </section>`;
133135 }
@@ -162,9 +164,10 @@ export function clock(input: {
162164 open?: number | undefined;
163165 q?: string | undefined;
164166 side?: SideFilter | undefined;
167 closing?: ClosingFilter | undefined;
165168 }): string {
166169 const key = Bun.hash(
167 JSON.stringify([input.who, input.on, input.notice ?? null, input.open ?? null, input.q ?? "", input.side ?? "", input.agent, input.waiting, input.tour]) +
170 JSON.stringify([input.who, input.on, input.notice ?? null, input.open ?? null, input.q ?? "", input.side ?? "", input.closing ?? "", input.agent, input.waiting, input.tour]) +
168171 input.deals.map(print).join(),
169172 );
170173 return pages(`${input.deals.length}:${key}`, () => render(input));
@@ -183,6 +186,7 @@ function render(input: {
183186 open?: number | undefined;
184187 q?: string | undefined;
185188 side?: SideFilter | undefined;
189 closing?: ClosingFilter | undefined;
186190 }): string {
187191 const { who, on, notice } = input;
188192 const agent = input.agent ?? defaults;
@@ -190,7 +194,8 @@ function render(input: {
190194 input.open === undefined ? undefined : input.deals.flatMap((d) => d.sent).find((m) => m.id === input.open);
191195 const q = input.q ?? "";
192196 const side = input.side ?? "";
193 const found = onSide(find(input.deals, q), side);
197 const closing = input.closing ?? "";
198 const found = closingIn(onSide(find(input.deals, q), side), on, closing);
194199 const deals = found.filter((deal) => !deal.closed && !deal.fell_through);
195200 const closed = found
196201 .filter((deal) => deal.closed && !deal.fell_through)
@@ -205,11 +210,11 @@ function render(input: {
205210 // A closed deal is not on the clock, but its check-in is a reminder for today.
206211 const today = [...deals, ...closed].map((deal) => reminderRows(deal, on, agent));
207212 const todayCount = today.reduce((n, t) => n + t.count, 0);
208 const none = (q !== "" || side !== "") && found.length === 0;
213 const none = (q !== "" || side !== "" || closing !== "") && found.length === 0;
209214 const toSend = today.reduce((n, t) => n + t.toSend, 0);
210215 const nothing =
211 q !== "" || side !== ""
212 ? `<p class="empty">No deal matches${q === "" ? " that side" : ` “${escape(q)}”`}. <a href="/">Clear</a></p>`
216 q !== "" || side !== "" || closing !== ""
217 ? `<p class="empty">No deal matches${q !== "" ? ` “${escape(q)}”` : closing !== "" && side !== "" ? " those filters" : closing !== "" ? " that closing" : " that side"}. <a href="/">Clear</a></p>`
213218 : `<p class="empty">No deals yet. To add your first deal, choose <b>New deal</b> and enter the address, the clients and the contract and closing dates; its clock starts at once. <a href="/deals/new">Open the first one</a>.</p>`;
214219 const timeline =
215220 deals.length === 0 && closed.length === 0 && fell.length === 0
@@ -254,7 +259,8 @@ function render(input: {
254259 <label for="find-q">Find a deal</label>
255260 <input id="find-q" class="input" type="search" name="q" value="${escape(q)}" maxlength="100" placeholder="Address, client or party" autocomplete="off">
256261 ${sideFilterSelect(side)}
257 <button class="button button-quiet">Find</button>${q === "" && side === "" ? "" : ` <a href="/">Clear</a>`}
262 ${closingFilterSelect(closing)}
263 <button class="button button-quiet">Find</button>${q === "" && side === "" && closing === "" ? "" : ` <a href="/">Clear</a>`}
258264 </form>
259265 <section id="clock" class="card clock" data-live="deal.changed">
260266 ${timeline}
@@ -268,7 +274,7 @@ function render(input: {
268274 </section>
269275 ${closedCard}
270276 ${restoreCard}
271 ${deals.length === 0 ? "" : weekCard(deals, on, q, side)}
277 ${deals.length === 0 ? "" : weekCard(deals, on, q, side, closing)}
272278 ${none ? "" : `<section id="today" class="card" data-live="deal.changed">
273279 <h2>Reminders for today <span class="muted">· ${toSend === 0 ? "all sent" : `${toSend} to send`}</span></h2>
274280 ${toSend === 0 ? "" : `<p><a class="button" href="/reminders/today${q === "" ? "" : `?q=${encodeURIComponent(q)}`}">Review all</a></p>`}
src/pages/closing-window.ts added +8
@@ -0,0 +1,8 @@
1 /** The clock's Closing filter: a select that is part of the Find form, so it narrows without script. */
2
3 import { escape } from "../http.ts";
4 import { CLOSING_CHOICES, type ClosingFilter } from "../closing-window.ts";
5
6 export const closingFilterSelect = (closing: ClosingFilter): string =>
7 `<label for="find-closing">Closing</label>
8 <select id="find-closing" class="input" name="closing">${CLOSING_CHOICES.map(([v, name]) => `<option value="${v}"${v === closing ? " selected" : ""}>${escape(name)}</option>`).join("")}</select>`;
src/pages/week.ts +4 −2
@@ -5,6 +5,7 @@
55 * only shows the Print button. Printing rules are the client timeline's.
66 */
77
8 import type { ClosingFilter } from "../closing-window.ts";
89 import { urgencyOf, spoken } from "../dates.ts";
910 import type { DealView } from "../deals.ts";
1011 import type { Agent } from "../reminders.ts";
@@ -23,12 +24,13 @@ const closingAt = (deal: DealView, d: DealView["deadlines"][number]): string =>
2324 return text === "" ? "" : ` · ${escape(text)}`;
2425 };
2526
26 export function weekPage(input: { who: Who; agent?: Agent | undefined; deals: readonly DealView[]; on: string; q?: string; side?: SideFilter }): string {
27 export function weekPage(input: { who: Who; agent?: Agent | undefined; deals: readonly DealView[]; on: string; q?: string; side?: SideFilter; closing?: ClosingFilter }): string {
2728 const { who, deals, on } = input;
2829 const agent = input.agent ?? defaults;
2930 const q = input.q ?? "";
3031 const side = input.side ?? "";
31 const kept = [q === "" ? "" : `q=${encodeURIComponent(q)}`, side === "" ? "" : `side=${side}`].filter((p) => p !== "");
32 const closing = input.closing ?? "";
33 const kept = [q === "" ? "" : `q=${encodeURIComponent(q)}`, side === "" ? "" : `side=${side}`, closing === "" ? "" : `closing=${closing}`].filter((p) => p !== "");
3234 const back = kept.length === 0 ? "/" : `/?${kept.join("&")}`;
3335 const groups = byDay(thisWeek(deals, on), on);
3436 const days =
src/routes.ts +10 −2
@@ -105,6 +105,7 @@ import { permit } from "./contract.ts";
105105 import type { AuditRow } from "./audit.ts";
106106 import { rpc } from "./mcp.ts";
107107 import { FIND_REFUSAL, find, query } from "./find.ts";
108 import { CLOSING_REFUSAL, closingFilter, closingIn } from "./closing-window.ts";
108109 import { SIDES, SIDE_REFUSAL, onSide, sideFilter } from "./side.ts";
109110 import { clock } from "./pages/clock.ts";
110111 import { dealSheets } from "./pages/sheet.ts";
@@ -816,7 +817,10 @@ export const routes: readonly Route[] = [
816817 const side = sideFilter(c.url.searchParams.get("side"));
817818 if (side === undefined)
818819 return html(refusedPage({ who: c.who, agent: agentFor(c), path: "/", heading: "The clock", error: "invalid", message: SIDE_REFUSAL }), 422, clear);
819 return html(clock({ who: c.who, waiting: waitingCount(deals.value, dayOf(agentFor(c)), agentFor(c)), tour: hasTourDeals(deals.value), agent: agentFor(c), deals: deals.value, on: dayOf(agentFor(c)), notice, open, q, side }), 200, clear);
820 const closing = closingFilter(c.url.searchParams.get("closing"));
821 if (closing === undefined)
822 return html(refusedPage({ who: c.who, agent: agentFor(c), path: "/", heading: "The clock", error: "invalid", message: CLOSING_REFUSAL }), 422, clear);
823 return html(clock({ who: c.who, waiting: waitingCount(deals.value, dayOf(agentFor(c)), agentFor(c)), tour: hasTourDeals(deals.value), agent: agentFor(c), deals: deals.value, on: dayOf(agentFor(c)), notice, open, q, side, closing }), 200, clear);
820824 },
821825 },
822826 {
@@ -838,7 +842,11 @@ export const routes: readonly Route[] = [
838842 const side = sideFilter(c.url.searchParams.get("side"));
839843 if (side === undefined)
840844 return html(refusedPage({ who: c.who, agent: agentFor(c), path: "/week", heading: "This week", error: "invalid", message: SIDE_REFUSAL }), 422);
841 return html(weekPage({ who: c.who, agent: agentFor(c), deals: onSide(find(deals.value, q), side), on: dayOf(agentFor(c)), q, side }));
845 const closing = closingFilter(c.url.searchParams.get("closing"));
846 if (closing === undefined)
847 return html(refusedPage({ who: c.who, agent: agentFor(c), path: "/week", heading: "This week", error: "invalid", message: CLOSING_REFUSAL }), 422);
848 const on = dayOf(agentFor(c));
849 return html(weekPage({ who: c.who, agent: agentFor(c), deals: closingIn(onSide(find(deals.value, q), side), on, closing), on, q, side, closing }));
842850 },
843851 },
844852 {
src/speed.test.ts +3 −1
@@ -133,6 +133,8 @@ const routes: [string, () => string][] = [
133133 // The Closed list is the clock's lower section: the fixture closes some deals.
134134 ["the clock with closed deals", () => "/?closed=1"],
135135 ["activity (as admin)", () => "/activity"],
136 ["the clock narrowed to this month", () => "/?closing=month"],
137 ["the printed week narrowed to this week", () => "/week?closing=week"],
136138 ];
137139
138140 test.each(routes)("%s answers in under 50 ms with 200 deals", async (_name, path) => {
@@ -194,7 +196,7 @@ describe("the sample book", () => {
194196 return mid(times);
195197 }
196198
197 test.each(["/", "/reminders/today", "/week", "/people", "/profile"])("%s answers in under 50 ms on the sample book", async (path) => {
199 test.each(["/", "/?closing=month", "/week?closing=week", "/reminders/today", "/week", "/people", "/profile"])("%s answers in under 50 ms on the sample book", async (path) => {
198200 forgetRationing();
199201 const took = await median(path);
200202 expect(took).toBeLessThan(BUDGET_MS);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs