mobile: fix flex-row text overflow; parallelize door-ingest poll; docs refresh

Mobile responsiveness: several flex rows had a `truncate` child without
`min-w-0`, so a long value (member/door name, channel name, published form
URL) would not shrink and pushed the row past the viewport on a phone. Added
`min-w-0` (with `flex-1`/`shrink-0` where needed) on the dashboard presence +
door-self-entry rows, the comms channel list, and the form-builder URL.

Door inbound poll: `/api/cron/door-ingest` now polls connections with
Promise.allSettled instead of a sequential loop, so one tick is bounded to the
slowest single poll rather than the sum of all per-call timeouts; each
connection stays isolated and the run is idempotent.

Docs: refreshed ARCHITECTURE (door epic marked complete P1-P5), the README
access-control feature bullet + project-status pointer, a historical banner on
the older AUDIT.md, a door-epic CHANGELOG entry, and the rolling HANDOFF audit
log (passes 65-68). No migration; no behavior change for existing users.
This commit is contained in:
Moheeb Zara
2026-05-20 21:55:49 -07:00
parent 0ac69abc8e
commit 32934124c2
10 changed files with 86 additions and 31 deletions
+3 -3
View File
@@ -215,7 +215,7 @@ export default function CommsClient({ member, space, channels }: Props) {
}`}
>
<Hash className="w-3.5 h-3.5 flex-shrink-0" />
<span className="truncate">{channel.name}</span>
<span className="truncate min-w-0">{channel.name}</span>
{channel.unread_count > 0 && (
<span className="ml-auto w-4 h-4 bg-primary text-white text-[9px] font-bold rounded-full flex items-center justify-center">
{channel.unread_count}
@@ -240,7 +240,7 @@ export default function CommsClient({ member, space, channels }: Props) {
}`}
>
<Hash className="w-3.5 h-3.5 flex-shrink-0" />
<span className="truncate">{channel.name}</span>
<span className="truncate min-w-0">{channel.name}</span>
</button>
))}
</div>
@@ -262,7 +262,7 @@ export default function CommsClient({ member, space, channels }: Props) {
<svg className="w-3.5 h-3.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg>
<span className="truncate">{channel.name}</span>
<span className="truncate min-w-0">{channel.name}</span>
</button>
))}
</div>
+2 -2
View File
@@ -31,8 +31,8 @@ export function DoorSelfEntry({ doors }: { doors: Door[] }) {
<div className="bg-card rounded border border-border divide-y divide-border">
{doors.map(d => (
<div key={d.id} className="flex items-center justify-between gap-3 px-4 py-3">
<span className="font-sans text-sm text-foreground truncate">{d.name}</span>
<Button size="sm" disabled={busy === d.id} onClick={() => open(d)}>
<span className="flex-1 min-w-0 font-sans text-sm text-foreground truncate">{d.name}</span>
<Button size="sm" className="shrink-0" disabled={busy === d.id} onClick={() => open(d)}>
{busy === d.id ? 'Opening…' : 'Open'}
</Button>
</div>
+1 -1
View File
@@ -65,7 +65,7 @@ export function PresencePanel({ present }: { present: Present[] }) {
<ul className="divide-y divide-border">
{present.map(p => (
<li key={p.id} className="flex items-center justify-between gap-3 px-4 py-2.5">
<span className="font-sans text-sm text-foreground truncate">
<span className="flex-1 min-w-0 font-sans text-sm text-foreground truncate">
{p.name}{p.isMe ? ' (you)' : ''}
{p.note && <span className="font-mono text-[10px] text-muted-foreground ml-2">{p.note}</span>}
</span>
+28 -18
View File
@@ -52,29 +52,39 @@ export async function POST(req: NextRequest) {
.limit(MAX_CONNECTIONS)
if (error) return NextResponse.json({ error: error.message }, { status: 500 })
let polled = 0
// Poll connections concurrently: each connection is independent, and the
// executor's per-call timeout means a sequential loop would take the SUM of
// all timeouts (a slow/unreachable fleet could run minutes and overlap the
// next tick). allSettled bounds a tick to roughly the slowest single poll;
// each connection is isolated so one misbehaving controller (untrusted,
// plaintext HTTP) cannot 500 the run or starve the rest. MAX_CONNECTIONS
// caps the fan-out.
const list = (conns ?? []) as unknown as IngestConn[]
const settled = await Promise.allSettled(
list.map(async c => {
const poll = await pollConnectionLog(admin, c)
if (!poll.ok) {
console.error(`[door-ingest] connection ${c.id}: ${poll.detail}`)
return { failed: true, inserted: 0, resolved: 0 }
}
const res = await ingestEvents(admin, c.space_id, c.id, poll.events)
return { failed: false, inserted: res.inserted, resolved: res.resolved }
}),
)
let inserted = 0
let resolved = 0
let failed = 0
for (const c of (conns ?? []) as unknown as IngestConn[]) {
polled++
// Isolate each connection: a single misbehaving controller (the body is
// untrusted, plaintext HTTP) must not 500 the whole run and starve the rest.
try {
const poll = await pollConnectionLog(admin, c)
if (!poll.ok) {
failed++
console.error(`[door-ingest] connection ${c.id}: ${poll.detail}`)
continue
}
const res = await ingestEvents(admin, c.space_id, c.id, poll.events)
inserted += res.inserted
resolved += res.resolved
} catch (e) {
for (const r of settled) {
if (r.status === 'rejected') {
failed++
console.error(`[door-ingest] connection ${c.id} threw:`, e instanceof Error ? e.message : e)
console.error('[door-ingest] connection threw:', r.reason instanceof Error ? r.reason.message : r.reason)
continue
}
if (r.value.failed) failed++
inserted += r.value.inserted
resolved += r.value.resolved
}
return NextResponse.json({ ok: true, polled, inserted, resolved, failed })
return NextResponse.json({ ok: true, polled: list.length, inserted, resolved, failed })
}