Skip to main content
Paul Welty, PhD AI, WORK, AND STAYING HUMAN

Work log: Scholexis — March 23, 2026

What shipped today

This was the biggest feature day in the project’s history. Five core CRUD features were built from scratch — assignments, tasks, events, calendar, and access grants — closing the gap between “dashboard with academic setup” and “functional academic task manager.” The app went from having 5 dead sidebar links to a fully navigable product with every page functional.

Core feature builds dominated the session. Assignments CRUD (#209) established the pattern: server actions with auth/validation/error handling, list page with StatusBadge and PriorityBadge, create/edit forms with course association, and delete with the styled ConfirmDialog. Tasks (#210) followed the same structure but added a key differentiator — inline status toggle using useTransition that lets students check off tasks directly from the list without opening the edit page. Completed tasks show with line-through and reduced opacity. Events (#211) introduced the all-day toggle that dynamically hides time inputs. The calendar (#212) aggregates all three data sources into a 7-column week grid with prev/next navigation via search params, color-coded items by type, and click-through to edit pages. The /schedule placeholder was replaced with a redirect to /calendar. Access grants (#213) took a different approach — no create/edit page routing, just a single page with two sections (“shared with others” + “shared with me”) and an inline grant form with email-based user lookup.

Security and hardening continued in parallel. Role validation in signup (#191) now whitelists only “student” and “parent” — preventing privilege escalation. Rate limiting (#194) was added to both login (5/email/15min) and signup (3/email/15min) using an in-memory sliding window. The AUTH_URL port mismatch (#195) was fixed locally. Dashboard queries (#193) were parallelized from 12+ sequential DB calls to a single Promise.all() batch — expected ~10x latency improvement.

UX polish included replacing all 5 browser confirm() dialogs with styled ConfirmDialog components (#192) using @base-ui/react AlertDialog with shame-free language. Loading spinners were added to all 9 dashboard route segments (#201). The weekType schema mismatch (#200) was fixed to accept all 5 UI values. Error logging (#203) was added to all 20 catch blocks across 8 server action files. The .env.local.example (#202) was updated to document all required environment variables.

Code quality cleanup rounded out the day. Tests were added for all 4 new Zod schemas (#219) — bringing the total to 101 tests. The duplicate formatDate() function was extracted from 7 files into a shared lib/format-utils.ts (#220). Unused CTEs were removed from access grants (#221). Silent return on unauthorized deletes was replaced with proper ValidationError responses (#222) across 3 action files.

Completed

  • #191 — Validate role field in signup against whitelist (PR #196)
  • #192 — Replace browser confirm() with styled confirmation dialogs (PR #197)
  • #193 — Parallelize getDashboardData() queries and add error handling (PR #198)
  • #194 — Add rate limiting to login and signup endpoints (PR #199)
  • #195 — Fix AUTH_URL port mismatch in .env (local fix)
  • #200 — Fix weekType schema mismatch (PR #205)
  • #201 — Add loading.tsx to 9 dashboard route segments (PR #206)
  • #202 — Update .env.local.example with all required environment variables (PR #207)
  • #203 — Add console.error logging to server action catch blocks (PR #208)
  • #204 — 5 sidebar links lead to non-existent pages (decomposed into #209-#213)
  • #209 — Build assignments list, create, edit, and delete pages (PR #214)
  • #210 — Build tasks list with inline status toggle (PR #215)
  • #211 — Build events CRUD pages (PR #216)
  • #212 — Build calendar week view (PR #217)
  • #213 — Build access grants management page (PR #218)
  • #219 — Add tests for 4 new validation schemas (PR #224)
  • #220 — Extract duplicate formatDate() into shared module (PR #225)
  • #221 — Clean up access grants — remove unused CTEs (PR #226)
  • #222 — Return proper error on unauthorized delete (PR #227)

Release progress

  • Next.js port: 102/105 closed (3 open — #64, #65 need human clarification, #223 needs decision on tokens)
  • v1.0: 6/6 closed

Carry-over

  • #223 (tokens page placeholder) labeled needs-clarification — awaiting human decision on whether to build PAT management or hide the sidebar link
  • #64 (production deployment) and #65 (data migration) — both needs-clarification, no new human replies
  • The in-memory rate limiter resets on server restart and doesn’t work across multiple instances — acceptable for pre-launch but must be upgraded before scaling
  • Access grants uses raw SQL for self-join table aliases — works but commented to explain why

Risks

  • Five new CRUD features (2,500+ lines) were built mechanically from the course pattern without browser testing. The forms, tables, and interactions should work based on pattern parity, but visual and functional testing in Chrome is needed before production.
  • The calendar query maps 3 different DB query shapes into a union type using object spread — TypeScript accepts it but extra properties silently leak through.
  • The computeWeeks extraction from the earlier session was not browser-tested in context of term creation.

Flags and watch-outs

  • Test count is now 101 (6 test files). All are pure logic tests — no database-mocked tests, no E2E tests. Auth flows and server action ownership still rely on manual testing.
  • The Next.js port milestone is at 97% completion (102/105). The remaining 3 issues all require human decisions, not code work. The codebase is functionally complete for the port.
  • The dashboard-content.tsx has its own formatDate() that uses a different format (weekday, no year) — this is intentional and was not extracted into the shared module.

Why customer tools are organized wrong

This article reveals a fundamental flaw in how customer support tools are designed—organizing by interaction type instead of by customer—and explains why this fragmentation wastes time and obscures the full picture you need to help users effectively.

Infrastructure shapes thought

The tools you build determine what kinds of thinking become possible. On infrastructure, friction, and building deliberately for thought rather than just throughput.

Server-side dashboard architecture: Why moving data fetching off the browser changes everything

How choosing server-side rendering solved security, CORS, and credential management problems I didn't know I had.

The work of being available now

A book on AI, judgment, and staying human at work.

The practice of work in progress

Practical essays on how work actually gets done.

When the queue goes empty

Most products don't fail at building. They fail at the handoff between building and becoming real. What happens when the code is done and the only things left are judgment calls?

When your agents start breaking each other's code

Two agents modified the same file independently and created database locks. The fleet hit 135 issues in one day — and the coordination problem that comes with it.

The removal tax

The most productive thing you can do with a product is take features away. Eighty-nine issues closed across eight projects, and the hardest lesson came from a pipeline that ran perfectly and produced nothing.