Website Information
Official Guide
Feedback
For any questions, email dev@setonschool.net.
Dev Log
Opening and closing the Official Guide full preview no longer remounts the PDF, so it does not flash back to Loading guide. The expanded page also fits the viewport without a leftover scrollbar.
- Single Document stays mounted in a body portal across expand/collapse
- Expanded page sized to fit the screen — no scrollbar
- Page raster size updates only at rest, not mid-animation
The Official Guide full-screen preview now portals above the site header so the page is never dimmed behind the banner. When the document is taller than the viewport, the panel scrolls with a visible scrollbar while the up/down page controls stay available.
- Expand overlay portaled at z-500 above the header
- Lighter scrim; document stays undimmed on top
- Overflow scroll when the page is taller than the screen
On Website Information, tapping the Official Guide preview now smoothly expands it to a near full-page view so the document is easy to read. You can scroll the page, turn pages with the same up/down controls, and close with Escape, the backdrop, or the close button.
- Click preview → FLIP-style expand to near full viewport height
- Scrollable high-res page; up/down page nav kept in a floating bar
- Escape / backdrop / close dismiss with matching reverse animation
Chat bubbles now allow selecting text to copy, and URLs in messages become real links. A tap opens the link in a new tab; a hold selects it. Long-press reactions still work on non-link text, and the native copy menu appears when text is already selected.
- Selectable message text (select-none removed from bubbles)
- Auto-link http(s), www, and bare domain.tld URLs
- Click opens link; hold selects without opening reactions
- Right-click copy works when text is selected
New Message email links opened the chat overlay on phones but stayed on the channel list instead of the thread whenever the link had a ticket and no task. Mobile now opens straight into the conversation — the same as desktop — for email deep-links, toasts, and message buttons.
- Mobile thread opens by default when chat launches for a ticket
- Message toast / email deep-link passes openMobileThread
- Task and request message buttons match on phones
Optional motion style props now match what done-section returns, clearing the TypeScript errors that would have blocked the production build on push.
- Request and task row motion.style made optional
- npx tsc --noEmit clean before ship
Opening Completed and task sections was animating past the real list height, then snapping back at the end. Open now measures the true auto height once, animates exactly to that pixel value with no end correction, and row enter/exit is opacity-only so table-row transforms cannot inflate the target.
- Measure with height:auto via getBoundingClientRect
- Animate to one target — no remeasure snap at the end
- Enter/exit keyframes drop translate that broke table row layout
Completed, Denied, Resolved, and task-group sections no longer stutter at the end of open. The height clip now grows to the full list in one motion instead of stopping at the viewport and snapping the rest, then releases to auto without a jump.
- Open height animates to full content, not just the visible band
- Settle matches px height before clearing to auto
- Removes the end-of-open snap that looked like a glitch
Ticket Completed, Denied, and Resolved dividers now share the same --done-open chevron path as task groups on both desktop and mobile, so the arrow finally animates on tickets. React no longer resets the CSS transition mid-tween, and small lists open and close a bit snappier.
- Manager and My Requests sections wired to shared sectionKey
- Desktop and mobile ticket dividers stay in sync
- Timed chevron tweens skip React setState until settle
- Faster small-list item and stagger timings (~220ms / 64ms)
Completed, Denied, Resolved, and task-group open/close no longer snap open or freeze at random. Fresh opens always animate from height zero, false off-screen snaps are gone, and the chevron updates live without React re-renders resetting the motion mid-flight.
- Fresh open forces height 0 before expanding
- Fallback visible band when rects are unreliable
- Chevron driven via DOM during motion, not per-frame setState
- Item animation timing no longer stomped by React style resets
Pushing to main now runs a Dev Log preflight that compares the real shipping diff to pending version entries, checks track and numbering rules, and blocks the push when code would ship without a correct log. Github.md walks the agent through measure → fix versions → sync → commit → stamp → push.
- scripts/dev-log/preflight.ts audits diff vs pending versions
- Fails when code changes lack Dev Log coverage
- Warns when claimed +/- diverge from git numstat
- Github.md and push rule require PASSED before origin/main
The Dev Log heading now shows the live platform version next to the project’s total source line count — counted from src, scripts, and migrations on each load — so the current size of the codebase sits beside the version at a glance.
- Heading format like 1.6.3 · 68,722 lines
- Counts TypeScript, CSS, SQL, and markdown under src/scripts/migrations
- Recomputed on each Website Information load
Each patch version badge in the Dev Log now uses the same kind colors as Feature, Improvement, Fix, and the rest, so you can scan a track and see the mix of change types without opening every row.
- 1.x.y pills tinted by release / feature / improvement / fix / chore
- Major and track pills stay neutral for hierarchy
The Dev Log panel drops the extra how-to copy and visual restyle so it matches the previous layout again. Organization stays the same — eras, tracks, and patches — without instructional banners or rearranged kind badges.
- Removed intro and “How to read this” copy
- Restored original row layout and kind-pill placement
- Majors stay closed by default like before
The Website Information Dev Log now reads as a clear hierarchy: major eras (v0 Pre-release, v1 Live platform), themed tracks under each era, and individual patches inside each track. Mixed 1.4 work was split so Classify Edit Quality stays on 1.4 and Tasks Workspace lives on 1.5, track names were shortened, and the panel explains how to read the structure.
- v0 / v1 eras with plain-language blurbs
- Each minor line is one named track with a version range
- Tasks Workspace renumbered to 1.5.x; Classify Edit Quality kept on 1.4
- Shorter track labels and kind pills on every patch
Clicking a section title while it is still opening now reverses into a close from the current height instead of waiting for the animation to finish. The chevron next to the label tracks open progress live — right when closed, down when open, and smoothly in between.
- Open and close are interruptible mid-animation
- Close continues from the live clip height
- Chevron angle matches 0–100% open progress
- Remaining duration scales with how far the section already moved
Idle chat prefetch no longer fires twelve full inbox opens on Tasks load. Request-detail prefetch skips attachment signing until a dialog opens, home polling is slower when nothing is analyzing, and list queries are capped so Ticket/Task pages stay bounded.
- Idle prefetch cut to a few request details; chat stays hover-based
- Detail API supports unsigned prefetch (?sign=0)
- Home poll 3s in analysis / 15s otherwise; dialog poll only while analyzing
- listMyTickets and listTasks capped; getTicketTask skips signing
Opening a message now loads only that ticket’s channels and thread first, then fills the Messages sidebar in the background. Warm opens paint from a 5-minute cache instead of force-refetching, and channel ensure/meta queries are batched so managers no longer rebuild the whole inbox on every click.
- Focus-ticket chat open path for instant thread paint
- Full inbox loads in the background after open
- Cached opens skip force-refetch; chat TTL extended to 5 minutes
- Batched channel ensure and chunked message-meta queries
Opening or closing a huge Completed or task group no longer spends time on rows you cannot see. Off-screen items snap instantly; the stagger and height wipe run only for the viewport band, still capped at two seconds.
- Off-screen rows skipped with display:none on close
- Open animates the visible band then snaps the rest
- Fully off-screen sections snap with no animation
- Duration still scales from visible count only, max 2s
Closing Completed, Denied, Resolved, and task groups no longer leaves a lagging blank surface after the cards fade. The height wipe now runs in real pixels on a linear timeline with the reverse stagger, and card fades finish early so the clip and content stay together.
- Pixel-height wipe instead of grid 1fr→0fr on close
- Linear timing matched to bottom→top reverse stagger
- Exit fade front-loaded so surfaces are gone before the wipe
Collapsing Completed, Denied, Resolved, and task groups no longer scales forever with list size. Small sections keep the natural stagger; large ones compress so the full close finishes in at most two seconds.
- Hard 2s ceiling on Done-section open/close timeline
- Stagger compresses first, then row animation if needed
- Small lists stay at the original ~0.5s+ cadence
Email template previews no longer use personal-looking sample names. Every preview person is now the fictional John Doe or Jane Doe pair, so test and admin screens cannot accidentally show a real coworker.
- Requester, invitee, and chat From use John Doe
- Manager and worker samples use Jane Doe
- Preview comment warns never to use real names
Chat photos in New Message emails now appear in the body as an Image block — after Latest message and before Open conversation — instead of as a downloadable attachment. Each photo fills a rounded card so it matches the rest of the Seton email layout.
- Image section with header after Latest message
- Photo fills a rounded corner card edge-to-edge
- Embedded via CID like the brand logo
- Prior image + caption flow still carries the photo
New Message and Chat Summary emails now deep-link to the ticket and task channel instead of dumping people on the home page. Workers land on Tasks, requesters on My requests, and the chat overlay opens immediately on the correct thread.
- Email Open conversation includes ticket, task, and chat=1
- Workers route to /tasks; requesters stay on /requests
- Global chat opener reads the deep-link on any page
- Sign-in preserves deep-link query params through redirect
New Message emails now include chat photos as real file attachments instead of text-only previews. If someone sends an image and then a caption, the follow-up email carries both the message and the photo from the prior message.
- Images on the latest chat message attach to New Message mail
- Prior photo within 15 minutes attaches with a text follow-up
- Photo-only messages preview as Sent an attachment
- Gmail send supports multipart file attachments alongside the logo
Denied tickets now sit in their own collapsible section after Completed (managers and requesters), instead of mixing with open work. Admins and developers get the same confirm-first reminder bell there, which emails requesters to open the ticket, leave feedback, and resolve.
- Denied section after Completed on Tasks and My Requests
- Bell on Denied tickets with confirm before send
- Deny resolve email links requesters to the request page
- Resolve reminder preference covers complete and denied
Admins and developers can now ring the bell on requested tickets to email category managers and ask them to set a completion date and time before assigning. Every bell — resolve or assign — opens a confirm dialog first so reminders are never one-click spam.
- Bell on Requested tickets emails managers to assign a date
- Bell on Complete tickets still reminds requesters to resolve
- Confirm Send Reminder popup on every bell
- New assign-reminder email and manager preference toggle
The chat message box on phones now grows with each new line as you type, so multi-line drafts stay fully visible and editable instead of clipping after two lines. It expands seamlessly up to a soft max, then scrolls — same feel as iMessage.
- Composer height tracks wrapped lines while typing
- Shrinks back after send or delete
- Soft max (~36vh) before internal scroll
The Official Guide now grows vertically into place on load instead of popping in, and page turns use a real old-out / new-in crossfade with a soft vertical slide. The brief mid-opacity flicker from the old strip fade is gone.
- Intro height grow with fade-in on first paint
- True crossfade between outgoing and incoming pages
- Gentle vertical slide matched to scroll direction
On each load the Official Guide measures the viewport and grows the rounded page frame to fill most of the screen, stopping where Feedback stays visible underneath. The page is large enough to read without covering the next section.
- Viewport-based page height on refresh and resize
- Leaves Feedback heading in view under the guide
- Keeps the rounded bordered page frame
The Official Guide page is smaller again and sits inside a rounded bordered frame that marks the page edges. Vertical page turns and preloading stay the same — only the oversized full-bleed layout was pulled back.
- Compact page height capped to the viewport
- Rounded bordered container around the page
- Help column returned to the standard max width
Official Guide page turns now slide vertically with a longer soft crossfade, and every page is pre-rendered so the first scroll no longer flashes a blank. The guide fills the wider card edge to edge, so the gray side gutters are gone.
- Longer vertical slide with gentle opacity crossfade
- All seven pages preloaded before turning
- Full-width guide — no gray side bars
- Up/down chevrons match the vertical motion
The Official Guide no longer scrolls inside a page. Each PDF page fits the frame, and wheel or arrow moves to the next page with a short slide fade. Page controls sit under the view as quiet text chevrons beside the page count.
- One full page visible — no in-page scroll
- Wheel and arrows share the same smooth page-turn
- Subtle chevrons beside Page X of Y under the viewer
Every /help load now fetches setonschool.net/tech live, finds whichever Google Drive guide link is published there, and loads that PDF. Changing the Drive file on the school site is enough — Help picks it up on the next refresh with no hardcoded file id.
- Live scrape of setonschool.net/tech on each /help visit
- PDF proxy rediscovers the Drive link with cache: no-store
- Viewer cache-busts when the published file id changes
- Graceful empty state if the tech page has no Drive link
Website Information now loads the public Seton Help User Guide PDF from the school tech page’s Google Drive link and shows it page by page in-app. Prev/next controls, keyboard arrows, and a proxied PDF route keep the seven-page guide readable without leaving Help.
- Auto-loads the Drive PDF linked from setonschool.net/tech
- Page-by-page viewer with prev/next and arrow keys
- Server proxy at /api/help/official-guide avoids Drive CORS
- Scrollable page frame with sticky navigation controls
The question-mark Website Information page now opens with an Official Guide card before Feedback, linking straight to the school’s tech guide at setonschool.net/tech. Staff and requesters get the documented how-to without hunting the school site separately.
- Official Guide card above Feedback
- Opens https://setonschool.net/tech/ in a new tab
- Same card layout as the rest of Website Information
Task groups now open and close like Completed, Due replaces Date in Group by, and the Group by control blur-fades with the Tickets/Tasks swap instead of snapping. Chat task channels stay collapsed until hover or selection so the sidebar stays readable.
- Every group bin uses Completed-style expand/collapse
- Group by Due with blur-fade on the header control
- Task sub-channels expand on hover or selection
- Panel table crossfade kept light so only Group by carries the soft blur
The tasks list now has a Group by dropdown in the same portal-select style as the rest of Help. Switching Date, Category, Requester, or Status regroups the rows under clean section headers, and the familiar FLIP motion slides each task into its new place.
- Group by Date, Category, Requester, or Status
- Section headers with counts for every bucket
- FLIP reorder animation when the grouping changes
- Preference remembered in local storage
Expired test-mode tickets are now checked whenever anyone loads the site, not only on the daily cron. The check runs with the service role after the response and never grants visitors delete access; a short cooldown keeps busy traffic cheap.
- Root layout schedules TTL cleanup on every visit
- Service-role only — rank and session do not matter
- Two-minute cooldown and single-flight guard
- Daily cron kept as a safety net
Requester and manager detail edits now run the same classify-quality retitle across every still-open task on the ticket, not a single-task title rewrite. Sibling titles stay coherent with the original request, then the ticket title rebuilds from the full set — and managers finally get the same AI pass requesters already had.
- One model pass rewrites all open task titles with sibling awareness
- Manager description edits trigger the same retitle chain as requesters
- Ticket title rebuilds from the full updated task set
- Single-task fallback kept when only one open task remains
Help notification emails for a ticket now share a stable Message-ID / thread key so replies and follow-ups group together in inboxes instead of scattering as separate conversations.
- ticketThreadMessageId helper for consistent thread keys
- Received and manager notify paths pass ticketId and Message-ID
Admins and developers can open Request Config on create-request to submit as another directory user and mark tickets as Test. Test tickets skip Help emails and are cleaned up by a daily cron so practice traffic does not clutter production.
- Request Config panel: requester picker and test-mode switch
- tickets.test_mode and submitted_by columns with RLS-aware my-tickets
- Test badge in analysis and My Requests
- Cron route to delete old test tickets
Classify prompts are back to the shorter style: similar multi-place jobs use one overall ticket title instead of listing every room. Campus place lists were dropped from the classify prompt to cut tokens, and the site prefers the model title over packing heuristics.
- Similar tasks → overall concept, do not list each place
- No campus places block on classify; lighter default prompt bundle
- finalizeTicketTitle prefers AI over people/place packs
- Removed extra description AI rewrite on task edits
Developers can view and edit the live classify, retitle, and summary prompts from Manage without redeploying for every wording tweak. Defaults live in code; the database stores overrides and reseeds lean bundles when the defaults revision bumps.
- AI Prompts panel on Manage for developers
- Shared prompt fragments composed into classify and retitle calls
- ai_prompts table migration with save and reset actions
Admin and developer notification lookups no longer query an invalid help_access_level value that could make PostgREST reject the whole request. Watch recipients also honor email preference switches for each kind.
- Dropped invalid tech_admin from admin watch level filter
- Admin notify path uses preference-aware watch lookup
- Failed lookups clear the cache instead of serving stale empty results
The /tasks Tickets table was rewriting stored ticket names on load, so the row title often disagreed with the real title and the tasks inside. Display now trusts the saved ticket summary and only rebuilds when it is missing or still a placeholder.
- asTicket no longer polish-rewrites finished ticket summaries
- ticketHeadline prefers the stored title for table and dialogs
Classify and retitle prompts now target compact task titles by character count (about 20–32), with examples like Computer #22 Power Problem. Task titles are no longer clipped after the model returns — the AI is expected to write a finished short label.
- Character-budget guidance in classify and task retitle prompts
- Removed post-process character clipping on task titles
- Colons still allowed in short finished titles
The Dev notification category is no longer nested inside Administrative. It sits as its own main group after Administrative, with Website feedback still available as a toggle for admins and developers.
- Dev email prefs render after Administrative, not inside it
Opening Website Information no longer expands the first Dev Log stage by default. Every major dropdown starts closed so the page stays compact until you open one.
- All Dev Log major sections start closed on /help
Account notification settings now include a Dev category nested under Administrative. Admins and developers can toggle Website feedback mail from the Help page, and that send now respects the preference instead of always forcing delivery.
- Dev group nested under Administrative in email prefs
- Website feedback toggle for Help-page developer mail
- Feedback emails honor the new preference
The Help feedback card no longer shows a close X. The send button now matches Submit request: muted with “Please provide more details” until the message is long enough, then it fades into the primary Send to developer action with animated Sending dots.
- Removed the Feedback card close button
- Blocked submit state mirrors the create-request button
- Label crossfades between more-details and Send to developer
Help-page developer feedback now shows the animated Sending dots, keeps the card height steady, and crossfades into a centered thank-you. The developer email uses the standard Seton template titled Website Feedback with User, Feedback, and Date and Time blocks.
- Sending… loading dots while the message goes out
- Locked-height crossfade into the centered thank-you
- Website Feedback email template with User / Feedback / Date and Time
A root Github.md playbook now tells any agent how to version the work, sync Supabase dev_versions, commit on main, and push so Vercel production redeploys — without needing prior chat context.
- Step-by-step push checklist in Github.md
- Dev Log and Supabase sync required before every ship
- Cursor rule points agents at the playbook on push requests
The Website Information page was crashing because it called a client-only helper to read the current Dev Log version. That helper now lives in the shared data module, and the Supabase configuration check is invoked as a function so the page loads again.
- currentDevVersion moved out of the client panel module
- isSupabaseConfigured() called correctly before loading the log
- /help recovers from the server-component crash
Closing a request no longer waits on every follow-up email and cache write before the celebration appears. The summary shows immediately, while a finalize pass finishes notifications and sync in the background with the save chip still tracking the work.
- Resolve celebration opens as soon as the AI summary is ready
- Background finalize for emails and cache updates
- Worker chat notes gathered with tighter queries
The Dev Log card now leads with the current version on the same row as its title, and the explanatory footer plus the aggregate date and file tally were removed so the first glance is just the version that is live and the tree beneath it.
- Current version shown top-right beside the Dev Log heading
- Aggregate date and file summary line removed
- Versioning explainer footer removed
An always-on project rule requires every future change to append its own version entry with real line counts before the work is considered done. The dev log will keep growing on its own as more agents contribute, rather than decaying the moment someone forgets to update it.
- Always-applied rule governing version numbering and entry quality
- Real measured line counts required, never estimates
- Append-only history that cannot be renumbered or tidied away
- Future contributions extend the log without being asked
Every commit in the repository's history was analysed and rebuilt into a versioned record: 49 pushes became 115 versions, because a push carrying several distinct changes was split into one version per change. Line counts were bucketed by feature area so each split entry carries its real share, and the reconstruction reconciles exactly with git at 762 files, 72,443 lines added and 11,413 removed.
- All 49 commits analysed and split into 115 discrete versions
- Per-area line-count bucketing so split entries keep real numbers
- Totals reconciled exactly against git history
- Re-runnable sync that upserts rather than duplicates
The platform can now account for itself. A public dev log on the Website Information page opens from major milestone to capability track to individual version, showing what each change delivered, the date it landed, and the exact lines added and removed. Progress stopped being something you take on trust.
- Public dev_versions table with read-only public access
- Three-level dropdown from major to minor to individual version
- Per-version dates, summaries, and added and removed line counts
- Rolled-up totals at every level of the tree
The developer watermark was reworked to surface more session context at a glance without getting in the way of the interface behind it.
- Watermark surfaces richer session context
- Less visual intrusion on the underlying page
The Help page gained a direct channel for feedback and bug reports, so a problem with the platform itself no longer has to be filed as a school help ticket.
- Feedback and bug report form on the Help page
- Messages delivered directly to the developer
- Report type selection between feedback and bug
Entering an unregistered email used to be a dead end. It now opens a prompt offering to register that address, and answering yes carries the email straight into sign-up where it fills itself in with a left-to-right blur reveal. The consent checkbox was restyled to match the rest of the site and the email opt-in now defaults on instead of asking.
- Unregistered email prompts an offer to register
- Email carried into sign-up and revealed with a blur wipe
- Consent checkbox restyled to the site theme
- Email opt-in defaults on, removing a needless choice
Layers of defensive post-processing built up while the classifier was unreliable were now actively degrading its output. With training and evals in place, the titles the model produces are used as written.
- Title post-processing removed
- Model output used verbatim
- Defensive rewriting from earlier versions retired
A type error in the new campus-locations module broke the production build. Cleared in a two-line change.
- Type error in the campus locations module resolved
The request surface and sign-in path were refined alongside the analysis rework to keep the whole flow consistent.
- Request view refinements
- Sign-in path polish
Acronyms and proper nouns were being flattened by title post-processing. The model now reports which words must keep their capitalisation, and the pipeline respects it.
- Model-declared casing preserved end to end
- Acronyms and proper nouns no longer flattened
The classifier was given the school's own building and room list as reference material, so a misspelled hallway or an abbreviated room number resolves to the real place without the model being forced to pick from a fixed list. The analysis animation was rebuilt in the same push.
- Campus building and room list supplied as classifier reference
- Misspelled and abbreviated locations resolved correctly
- Reference data cached for the process lifetime
- Analysis animation rebuilt
The reclassify schedule was returned to a daily cadence so deploys stay inside the hosting plan's cron allowance.
- Daily reclassify cadence restored
An empty commit confirming automatic deploys resumed after the repository was reconnected to the host.
- Deploy trigger after repository reconnect
An empty commit verifying the deploy pipeline after checking the repository connection.
- Deploy trigger after GitHub connection check
Compound requests were leaking one task's qualifying clause into another. Clauses are now scoped to the task they belong to, and the access-request interface was tidied in the same pass.
- Qualifying clauses scoped per task
- Access-request interface tidied
An empty commit to force a fresh production deploy of the analysis hang fix.
- Deploy trigger for the analysis hang fix
Analysis hung in production because the serverless function returned before the background work finished. Awaiting classification inside the after-response hook let the platform keep the function alive to completion.
- Classification awaited inside the after-response hook
- Production analysis hang resolved
Supporting adjustments across the manager panel and email templates finished the async release.
- Manager panel adjustments for async state
- Email template touch-ups
With analysis running in the background, the requester view was rebuilt to stream status as it changes, so waiting looks like progress rather than a stalled page.
- Streaming analysis status on the request view
- Progressive task reveal as classification completes
The cost ledger got a front end. A developer panel breaks spend down by purpose and model, with the exact prompt and response for any call one click away.
- Usage panel grouped by purpose and model
- Token and dollar totals per run
- Prompt and response inspection per call
Every model call is now recorded: purpose, model, token counts, duration, and estimated cost in dollars. Eight migrations, eight hundred lines, no deletions. AI spend became a measured number rather than a monthly surprise.
- Per-call usage ledger with input and output tokens
- Estimated cost in USD stored per call
- Call duration and prompt or response capture
- Full audit trail of every classification run
Nineteen hundred lines of server work restructured actions, data access, and the catalog layer around the asynchronous model, replacing the background worker and processing queue with something simpler and more reliable.
- Server actions restructured for async analysis
- Analysis hold module replacing the processing queue
- Catalog layer reworked for live category reads
- Client background worker component removed
Classification was rebuilt as a background pipeline. Seventeen hundred lines added and more than eight hundred retired left submission instant: the request is accepted immediately and the model works behind it while the requester watches progress arrive.
- Background classification pipeline
- Instant request submission with no model wait
- Analysis hold coordination replacing the claim queue
- Over eight hundred lines of synchronous code retired
Fast successive edits could start overlapping classification runs and leave the ticket holding whichever finished last. Runs are now ordered so tasks are retitled before the ticket that contains them, and splitting is preferred over merging.
- Overlapping classification runs prevented
- Tasks retitled before their parent ticket
- Task splitting preferred over merging
Retitling after an edit now runs the same title rules as first-pass classification, so a title cannot drift into a different style just because it was regenerated.
- Shared title rules across classify and retitle
- Style drift on regenerated titles eliminated
The biggest simplification of the project. Seven hundred lines of multi-stage prompt orchestration were replaced by a single well-built pass that reads categories live from Supabase, running faster and cheaper while keeping the accuracy the eval suite had just proven.
- Multi-stage prompt chain collapsed to a single pass
- Categories read live from Supabase, never hard-coded
- Seven hundred lines of orchestration deleted
- Lower latency and lower cost per classification
Staff arriving from a recognised school domain get a purpose-written onboarding email instead of a generic notice.
- Domain account onboarding email
- Public URL handling for email links
Classifier quality stopped being a matter of opinion. A graded eval harness scores model output against expected results across a fixture set, so a prompt change can be measured instead of guessed at.
- Graded eval harness with scored fixtures
- Model and pass comparison runs
- Regression detection before deploy
- Nine hundred lines of test infrastructure
The milestone release. Thirteen hundred lines of few-shot training turned the classifier from a general-purpose model with instructions into a system tuned on real Seton requests. Worked examples, title polish rules, and category grounding took triage from usable to dependable, and marked the platform as production software.
- Few-shot prompt training on real request examples
- Title polish rules producing consistent, readable titles
- Category grounding tied to the live Supabase catalog
- Classification quality lifted to production standard
A broad tidying pass across server helpers, task views, and auth utilities cleared the accumulated rough edges before the push to a trained classifier.
- Server helper consolidation
- Task and auth utility cleanup
- Nearly two hundred lines of dead code removed
Every notification body was rewritten to lead with what changed and what the reader should do about it.
- Notification copy rewritten across all message types
- Clear action framing in each email
Unread counts now update over a realtime subscription and new messages raise a toast, so a conversation cannot go unnoticed while someone is on another page.
- Realtime unread message counts
- Toast notifications for incoming messages
- Shared message event bus refined
A near-even rewrite of the staff panels, removing almost as much as it added, left the manager tooling meaningfully simpler to use.
- Manager and admin panels simplified
- Preconfigure and invite forms refined
- Almost four hundred lines of redundant code removed
Three migrations and seven hundred and forty-eight lines with no deletions gave visit sessions and their grouping keys a permanent home.
- Visit session and grouping tables
- Session category classification
- Indexes supporting the Data Log queries
The Data Log learned to group raw page views into coherent visit sessions, turning a firehose of events into a readable story of what someone actually did.
- Page views grouped into visit sessions
- Session grouping and cleanup logic
- Visit timeline rendered in the Data Log
Task titles and descriptions were retrained to state what was asked without embellishment, guesswork, or invented urgency.
- Titles and descriptions restricted to stated facts
- Embellishment and invented detail removed
- Nearly two hundred lines of old prompt logic retired
Clear requests were being parked for human review too eagerly. Needs Review is now reserved for genuinely unintelligible submissions, so a plainly worded IT request becomes real tasks immediately.
- Needs Review limited to unintelligible requests
- Clear requests classified straight into tasks
- Two hundred lines of decision-boundary logic
A seven-hundred-line supporting pass tightened the mobile Tasks and Tickets views and rebuilt the email layout shell that every message renders inside.
- Mobile Tasks and Tickets layout tightened
- Email layout shell rebuilt
- Feedback action button component added
A ticket's title is now composed from the names of the tasks inside it, so the queue reads as a list of real work instead of a list of paraphrased complaints.
- Ticket titles composed from constituent task names
- Title regenerated when tasks change
The invitation path was rebuilt end to end with proper auth callback and confirm routes, so an invite link reliably lands the recipient in a configured account.
- Auth callback and confirm routes
- Invite link generation and verification
- Access level applied on link acceptance
Nine migrations and thirteen hundred lines gave the Data Log and the invitation system durable, indexed storage with exactly one deletion across the whole push.
- Data Log event and session tables
- Invitation and preconfiguration schema
- Developer-only access functions
Sixteen hundred lines of administration tooling. Admins can now manage users directly and preconfigure an account, complete with access level and category assignments, before that person has ever visited the site.
- Users administration panel
- Preconfigure accounts ahead of first sign-in
- Access level and category assignment from the UI
Nine hundred lines of built-in observability. The Data Log surfaces database activity, classification runs, and user sessions inside the app itself, so diagnosing behaviour no longer means opening a SQL console.
- In-app Data Log panel for developers
- Database activity and classification run history
- Session and visit inspection
- Developer-only access enforced in policy
Admins are now told when someone registers or returns, giving them a live view of who is using the platform without opening the database.
- Admin notification on new account creation
- Admin notification on returning sign-in
Requester and worker feedback now appears on ticket and task details for staff to review, deliberately hidden from the requester so candid notes stay candid.
- Feedback display on ticket and task details
- Visibility restricted to staff roles
- Requester and worker notes shown separately
A small save indicator now confirms writes landed, and session handling was tidied alongside it.
- Save indicator on inline edits
- Session resolution cleanup
The classification prompt was cut back hard, removing more lines than it added while holding accuracy steady. Cheaper calls, same result.
- Prompt reduced with no accuracy loss
- Net removal of thirteen lines of prompt logic
Ticket progress now reports the least-advanced task rather than an average, so a ticket is only as far along as its slowest piece of work. Less flattering, considerably more truthful.
- Progress derived from the least-advanced task
- Consistent progress reporting across all views
Preferences and reactions were given durable storage with the server-side helpers needed to read and apply them on every send.
- Preference and reaction tables with policies
- Server helpers applying preferences at send time
- Recipient resolution respecting every switch
Conversations got twelve hundred lines richer. Files can be attached directly to a message and anyone in the thread can react to it, so acknowledgement no longer costs a full reply.
- File attachments on chat messages
- Emoji reactions on any message
- Reaction counts aggregated per message
Notification volume became a per-person choice. A role-aware preference system lets each account switch off exactly the categories of mail it does not want, with managers able to narrow alerts to the categories they own.
- Role-based email preference model
- Per-kind and per-group notification switches
- Category filtering for manager alerts
- Master switch that overrides every sub-preference
A supporting pass across shared components and email copy kept the hardening release feeling finished rather than merely secure.
- Shared component refinements
- Person name and email hover cards
- Email copy adjustments
The new limits needed somewhere to live. Two migrations added rate-limit counters and access-request records with policies to match.
- Rate-limit tracking tables
- Access-request records and policies
Images shared in conversation render as inline thumbnails that open to full size, so a photo of the problem sits in the thread rather than behind a download.
- Inline chat attachment thumbnails
- Full-size preview on tap
Users who need more access can now request it in-app and have an admin approve it by link. Scheduled jobs were locked behind a shared secret so only the scheduler can trigger them.
- In-app account access request flow
- Admin approval by emailed link
- Cron endpoints protected by a shared secret
Ticket approval was rebuilt so authorisation is verified server-side at the moment of approval rather than inferred from what the interface allowed.
- Approval authorisation verified on the server
- Manager panel wired to the guarded path
When one request carries several problems, each photo now belongs to the task it actually depicts, so a worker opening a task sees only the images that matter to them.
- Per-task image assignment
- Attachment thumbnails with preview
- Images scoped to the task that needs them
Requests gained reference material and a far more informative progress display, so a requester can see not just that work is happening but what stage it has reached.
- Reference attachments on requests
- Detailed multi-stage progress display
- Progress derived from real task state
A thousand lines of classifier hardening treated request text as untrusted input, so a requester cannot talk the model into ignoring its instructions.
- Request text handled as untrusted model input
- Prompt-injection resistant instruction framing
- Output validated before it reaches the database
Fourteen hundred lines of server-side guards, rate limits, and input validation went in behind an interface that looks exactly the same. The security posture changed completely; the user experience did not change at all.
- Server-side rate limiting on submission paths
- Input validation and sanitisation before every write
- Authorisation re-checked on the server for each action
- Abuse surface closed with no visible UI change
Workers are rarely at a desk. Chat and the task list were reworked for phones so a conversation can be opened and work can be started from the hallway.
- Chat usable on phone-sized screens
- Task list touch targets and layout reworked
- Start-work flow reachable on mobile
Concurrent workers were classifying the same ticket twice and burning tokens on duplicate work. A database-level lock now holds a ticket for the duration of its analysis, guaranteeing exactly one pass.
- Database-level analysis lock per ticket
- Duplicate classification eliminated
- Wasted model spend removed
Mail now leaves from help.setonschool.net with admins on BCC for visibility. In the same pass, a ticket with only one task shows a single conversation instead of a redundant thread list.
- Sending domain moved to help.setonschool.net
- Admins BCC'd for oversight
- Single-task tickets show one unified chat thread
Notifications became event-driven. Instead of firing on every save, email now goes out when work genuinely changes state, which means a task completion reaches the right person and nothing else does.
- Event-driven email triggered by real state changes
- Task completion notices routed to the right recipients
- Recipient resolution centralised in one module
- Redundant notification sends eliminated
Help stopped hiding behind a sign-in wall. Public pages got real, readable URLs, so a signed-out visitor clicking a header link lands on the page they asked for instead of bouncing through authentication.
- Public access to Help pages without sign-in
- Readable canonical URLs for every route
- Header navigation fixed for signed-out visitors
- Twenty-seven files restructured around public routing
Three type errors surfaced only in the production build after the large dispatch push. All three were cleared in a three-line follow-up.
- Three production-only type errors resolved
A discreet developer watermark landed as the first piece of in-app observability, marking non-production surfaces at a glance.
- Developer watermark on non-production surfaces
Assignment and resolution notices got purpose-written templates rather than reusing a generic body, so each message says exactly what changed.
- Assignment and resolution email templates
- Message copy tailored per event
Sign-up was reworked to carry access level and category context through registration, so a new staff member arrives with the right permissions already applied.
- Access level resolved during sign-up
- Category context carried through registration
- Access badge shown across staff surfaces
The classifier was hardened for production traffic, with steadier category selection and cleaner handling of requests that do not fit the mould.
- More consistent category selection
- Better handling of ambiguous requests
- Analysis route resilience improvements
Watching a request get analysed became the signature moment of the product. A crossfade sequence animates the transition from raw text to structured tasks, backed by eight hundred lines of new interface work.
- Request analysis crossfade animation
- Expanded shared UI kit
- Progress display driven by real task state
Tasks gained a full lifecycle with a worker completion review step, so finished work is checked before the requester is told it is done.
- Complete task lifecycle from open to reviewed
- Worker completion review step
- Task views updated for the new states
A new home workspace became the requester's single destination: submit, track, chat, and confirm resolution without ever navigating away.
- Home request workspace component
- Submit and resolve confirmation flows
- Request form rebuilt on the workspace model
Twelve migrations, fourteen hundred lines, and not a single deletion: assignment history, notification state, and resolution tracking all got first-class tables and policies.
- Assignment and resolution tracking tables
- Notification state persisted per recipient
- Row-level security policies for every new table
Fifteen hundred lines of typed server actions and data helpers gave every new surface a single, validated path to the database instead of scattered queries.
- Typed server actions for assignment and resolution
- Consolidated data-access helpers
- Validation centralised ahead of every write
Chat matured into a real thread model, and completed work moved into a dedicated done section so the active queue only shows what still needs a person.
- Per-task conversation threads
- Done section for completed work
- Open-ticket query helper shared across panels
The largest push of the project so far: more than ten thousand lines that closed the loop from request to resolution. Staff can now assign work, notify the right people, and resolve a ticket from one place, with the requester kept informed at every step.
- End-to-end assignment workflow for managers
- Resolution confirmation with requester notification
- Manager ticket panel expanded past fifteen hundred lines
- Category ownership wired into dispatch decisions
The phone experience caught up with the desktop one. Modals were rebuilt for small screens, a safe-area aware banner stopped notches from clipping content, and classifier priority output was formatted for narrow layouts.
- Modals rebuilt for small screens
- Safe-area aware banner for notched devices
- Classifier priority formatting for narrow layouts
The model was hallucinating tasks nobody asked for. A rewritten prompt grounds every output in the requester's own words, so the task list reflects the request instead of the model's imagination.
- Prompt rewritten to ground output in the source request
- Invented and speculative tasks eliminated
- Task text constrained to requester-supplied detail
Analysis throughput reached roughly one ticket per second on gpt-4.1-nano, turning triage from something you wait for into something that has already happened.
- Throughput tuned to about one ticket per second
- Model selection widened to gpt-4.1-nano
- Analysis timing recorded in the database
The triage engine was swapped from Groq to OpenAI's gpt-4o-mini. A provider migration that touched six files and shipped net-negative, thanks to the abstraction that had been kept in place for exactly this moment.
- Classification provider migrated to OpenAI gpt-4o-mini
- Model configuration centralised behind one module
- Provider swap completed with fewer lines than it removed
A missing manager email on the task fallback path failed the production type check. One line closed it.
- Manager email typed on the task fallback branch
The reclassify job was scheduled more often than the hosting plan allows, which blocked deploys outright. Moving it to a daily cadence restored the pipeline.
- Reclassify cron reduced to once daily
Managers stopped writing the same message twice. A card-based template picker drops pre-written email bodies into the compose flow.
- Email template cards in the manager flow
- Shared template definitions
Account settings and the sign-in path were tightened up alongside the redesign so the entry experience matched the quality of the rest of the app.
- Account settings form rebuilt
- Session resolution cleanup
- Access badge display for staff roles
Analysis moved off the submit path. A dedicated API route plus a scheduled reclassify job meant a requester never waits on the model, and tickets that need another pass get one automatically.
- Dedicated analyze API route
- Scheduled reclassification cron job
- Background analysis worker on the client
Eight hundred lines of styling turned state changes into motion. Crossfades, transitions, and a widened token set made the interface explain itself as it updated.
- Analysis crossfade and transition system
- Expanded colour, spacing, and motion tokens
- Reusable UI primitives for buttons, inputs, and indicators
Task detail became the place work actually happens, with inline editing, assignment, and status changes on the page rather than behind separate forms.
- Inline editable task fields
- Assignment and status changes without leaving the page
- Task detail wired to the client cache
The requester view was rebuilt as a workspace: submit a request, watch it get analysed, and follow every task on one page instead of hopping between routes.
- Single-page requester workspace
- Live analysis state on the submitted request
- Request progress reflecting real task status
Nineteen migrations in one push gave chat, category ownership, and reclassification their own properly indexed and policy-protected home in Postgres.
- Message and conversation tables with access policies
- Category ownership and ranking schema
- Reclassification bookkeeping columns
- Indexes for the new query paths
The manager panels were rebuilt around the new cache and chat, replacing the first-generation category and review components with a consolidated managers panel that does the work of three.
- Category managers panel replacing two earlier components
- Manager tickets panel rebuilt on cached data
- Retired the standalone classification review surface
The app stopped feeling like a website and started feeling like software. A client-side cache with prefetching and time-to-live invalidation serves views instantly, then reconciles against the server in the background.
- Client cache with TTL invalidation and prefetching
- Optimistic updates reconciled against server state
- Shared fetch-or-cache helper across all panels
- Navigation between Help surfaces made near-instant
Requesters and workers can now talk inside the ticket. Nearly three thousand lines of chat delivered threaded messages, read state, and live updates, ending the email chains that used to carry the actual details of a job.
- Threaded ticket conversations between requesters and staff
- Realtime message delivery and unread counts
- Attachment previews inside the conversation
- Message event bus shared across panels
Ownership became a chain of responsibility. Each category takes an ordered list of managers, and a manager can be added before they have ever signed in: a placeholder account waits for them and is claimed automatically when they verify their email.
- Ordered manager ranking per category
- Placeholder accounts for staff who have not signed in yet
- Claim-on-verify linking placeholders to real accounts
- Four hundred and fifty lines of new schema and policy
Finished tickets were crowding the manager queue. Resolved work now lives in its own section, and one status colour language was applied everywhere so a glance tells you the state.
- Resolved tickets separated in the manager view
- Unified status colours across every surface
Classification was failing on complex requests because the response schema was too elaborate for the model to satisfy. Flattening it and classifying before summarising cut the failure rate and removed more code than it added.
- Flattened structured-output schema
- Classify-then-summarise ordering
- Net reduction of eighty-five lines of prompt plumbing
The classifier got context. Feeding it written descriptions of every category taught it to split a rambling request into the right number of tasks and route each one to the team that actually owns it.
- Category descriptions supplied to the classification prompt
- Multi-task splitting from a single request
- Simplified submit confirmation reflecting the split result
Assigned work no longer waited on paperwork. Workers now see tasks the moment they are assigned, even while the parent ticket is still pending manager approval.
- Task visibility decoupled from ticket approval
- New row-level security policy for assignee reads
With staff tooling in place, the requester side was trimmed back to essentials and both audiences were moved onto one shared page shell so layout stopped drifting between them.
- Unified page shell across requester and staff routes
- Requester surface simplified around submit and follow
- Classifier wiring updated for the new category model
Managers can now staff a category by typing an email address. The invitation arrives as a branded message with a link that provisions the account and its access level on acceptance.
- Invite-worker email template
- Invitation form wired into category ownership
- Access level applied on invite acceptance
Access stopped being manual. Anyone signing up from a recognised school domain is elevated to trusted user automatically, and new tables backed the worker invitation flow.
- Automatic trusted-user access for school email domains
- Invitation tables and policies
- Domain matching helpers in the auth layer
Three hundred lines of chat scaffolding went in ahead of the feature itself, establishing the message surface that requesters and workers would later use to settle details without email ping-pong.
- Ticket chat component scaffold
- Message rendering and composition groundwork
The task table was rewritten around real staff workflows, trading a generic grid for sortable, role-aware columns that show the next action rather than raw fields.
- Rewritten task table with role-aware columns
- Panel tabs for switching task views
- Cleaner empty and loading states
Routing was rebuilt around roles. A dedicated config surface replaced the ad-hoc admin route, and pages were reorganised so each audience lands where its work lives.
- Dedicated config page replacing the admin route
- Role-aware route structure across the app
- Server data helpers consolidated behind typed functions
Managers got a command centre. A single workspace now holds the ticket queue, category ownership, and staffing controls, replacing the thin admin page with more than two thousand lines of purpose-built tooling.
- Manager workspace shell with tabbed panels
- Ticket queue with filtering and bulk review
- Category management with per-category ownership
- Staff-facing controls separated from requester views
A visual pass over the two surfaces staff look at most. New header iconography and a decluttered task list made the difference between working views and prototype views.
- Redrawn Help header icons
- Task list layout and density cleanup
Editing a request stopped feeling like filing paperwork. Saves apply optimistically instead of waiting on a round trip, and when a title changes the old text morphs into the new one so the update reads as continuous rather than jarring.
- Optimistic saves on request field edits
- Character-level morphing animation between task titles
- Reduced perceived latency on the my-request surface
Staff moving between school hostnames stopped having to sign in twice. Auth cookies are now shared across the school domain, with canonical redirects prepared so one address is always the real one.
- Auth cookies shared across school subdomains
- Canonical redirect groundwork for the deployment domain
iPhone users were being silently signed out. The shared cookie domain is now applied only on hosts it actually matches, so sessions persist on iOS Safari the same way they do everywhere else.
- Cookie domain scoped to matching hosts only
- Session persistence fixed on iOS Safari
A stray prop on the inline textarea broke the production type check. Caught and cleared in minutes, keeping the deploy pipeline green from the project's first day.
- Removed invalid rows prop breaking the production build
Release discipline landed early. The repository standardised on exactly two long-lived branches, `main` for production and `development` for staging, documented so every later change had one obvious path to production.
- Documented main and development branch policy
- Contribution guidance for deploy-safe pushes
The headline capability arrived in the very first push: a language model reads each incoming request, decides which category owns it, and drafts the tasks needed to resolve it. Automated triage was the founding premise of the product, not an afterthought.
- AI classification of free-text requests into owned categories
- Model-drafted task breakdown for each ticket
- Structured-output schema keeping model responses parseable
- Groundwork for the chat and activity log surfaces
The platform learned to speak up. A hand-built HTML email layer with a shared template shell meant sign-in codes and ticket notifications arrived looking deliberate instead of like raw system output.
- Branded HTML email layout and shared components
- One-time-code delivery email
- Ticket notification messages
- Public URL resolution so email links work in every environment
Requests became work. A ticket now fans out into individual tasks with their own status, assignee, and detail page, giving staff a board to work from instead of an inbox to dig through.
- Task table with status and assignment columns
- Task detail page with editable fields
- Ticket review surface for staff
- Status colour language shared across every view
The front door opened. Anyone at the school could describe a problem in plain language, attach a photo of it, and watch the request move through submission into review without needing to know who owns the fix.
- Plain-language request form with validation
- Image attachment upload backed by Supabase Storage
- Submission confirmation and request progress display
- My-requests list so requesters can follow their own tickets
Sign-in launched without a single password to steal. Accounts authenticate with emailed one-time codes, and a tiered access-level system separated requesters, trusted users, workers, managers, admins, and developers from the very first release.
- One-time-code sign-in and sign-up flows
- Six-tier access model from requester through developer
- Session helpers resolving the signed-in user and permissions
- Account settings page for name and email preferences
The data model shipped locked down on day one. Fifteen migrations defined tickets, tasks, users, categories, and rooms, and every table came out of the gate with row-level security policies rather than having them bolted on later.
- Tickets, tasks, users, categories, and rooms tables
- Row-level security enabled on every table from the first migration
- Access-level enum driving read and write permissions
- Security-definer helper functions for policy reuse
An empty folder became a deployable application. This first push laid down the entire Next.js App Router skeleton, the Supabase client layer, the typed server-action plumbing, and a full design system of theme tokens so every screen built afterwards would already look like one product.
- Next.js App Router project with TypeScript, ESLint, and Turbopack
- Supabase browser, server, and admin clients with typed helpers
- Design-system tokens for colour, surface, border, and status states
- Shared page shell, layout, and header navigation
- Deployment configuration and environment contract