Skip to content

Commit a0e3b45

Browse files
bchapuisclaude
andcommitted
Redesign email inbox as flat two-pane view with search
Restyle the read-only mailbox to match the admin support layout: flat two-pane (thread list + detail), no cards, top search bar. Drops the write-oriented chrome (view dropdown, new thread, archive, reply box). Wire server-side thread search through the mailbox DO, route, and service so the search bar filters by subject or sender. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 20a0407 commit a0e3b45

4 files changed

Lines changed: 226 additions & 147 deletions

File tree

apps/api/src/durable-objects/mailbox-do.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -381,19 +381,38 @@ export class MailboxDO extends DurableObject<Bindings> {
381381
async listThreads(
382382
emailId: string,
383383
limit: number,
384-
offset: number
384+
offset: number,
385+
search?: string
385386
): Promise<MailboxThreadRow[]> {
386387
this.ensureSchema();
387-
const rows = this.ctx.storage.sql
388-
.exec(
389-
`SELECT id, email_id, subject, from_email, last_message_at, created_at
390-
FROM threads WHERE email_id = ?
391-
ORDER BY last_message_at DESC LIMIT ? OFFSET ?`,
392-
emailId,
393-
limit,
394-
offset
395-
)
396-
.toArray() as Record<string, unknown>[];
388+
const term = search?.trim();
389+
const sql = this.ctx.storage.sql;
390+
// Escape the LIKE wildcards a user might type so they match themselves
391+
// rather than acting as patterns.
392+
const like = term && `%${term.replace(/[\\%_]/g, "\\$&")}%`;
393+
const rows = (
394+
like
395+
? sql.exec(
396+
`SELECT id, email_id, subject, from_email, last_message_at, created_at
397+
FROM threads
398+
WHERE email_id = ?
399+
AND (subject LIKE ? ESCAPE '\\' OR from_email LIKE ? ESCAPE '\\')
400+
ORDER BY last_message_at DESC LIMIT ? OFFSET ?`,
401+
emailId,
402+
like,
403+
like,
404+
limit,
405+
offset
406+
)
407+
: sql.exec(
408+
`SELECT id, email_id, subject, from_email, last_message_at, created_at
409+
FROM threads WHERE email_id = ?
410+
ORDER BY last_message_at DESC LIMIT ? OFFSET ?`,
411+
emailId,
412+
limit,
413+
offset
414+
)
415+
).toArray() as Record<string, unknown>[];
397416
return rows.map((r) => ({
398417
id: r.id as string,
399418
emailId: r.email_id as string,

apps/api/src/routes/emails.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ emailRoutes.get(
269269
z.object({
270270
limit: z.coerce.number().min(1).max(100).default(50),
271271
offset: z.coerce.number().min(0).default(0),
272+
search: z.string().trim().optional(),
272273
})
273274
),
274275
async (c) => {
@@ -281,11 +282,12 @@ emailRoutes.get(
281282
return c.json({ error: "Email not found" }, 404);
282283
}
283284

284-
const { limit, offset } = c.req.valid("query");
285+
const { limit, offset, search } = c.req.valid("query");
285286
const threads = await mailboxStub(c.env, organizationId).listThreads(
286287
id,
287288
limit,
288-
offset
289+
offset,
290+
search
289291
);
290292

291293
const response: ListMailboxThreadsResponse = {

0 commit comments

Comments
 (0)