DEV Community

Cover image for Hearthafter: Good Company, a Place to Haunt
Bella Baelfire
Bella Baelfire

Posted on AI-assisted

Hearthafter: Good Company, a Place to Haunt

Sanity Challenge Path Two Submission

This is a submission for the Sanity Challenge, Path Two: Vibe-Code Something Strange.

I wanted to bring your Sanity to some of my insanity.

What I Built

The world is suddenly overcrowded with ghosts. Somebody has to organize the living arrangements.

I built Hearthafter, a newly opened placement service operated by the fictional Office of Living & Departed Affairs. Living households offer space in homes they already have. Departed people need somewhere to belong. The Office is trying to keep the peace.

The trouble starts around Ashburn, Virginia. Massive data-center demand leads to new power-generation systems and expanded transmission infrastructure. An unforeseen effect makes the departed visible again, first around Northern Virginia’s Data Center Alley and then farther afield. People call it the Overlap.

That creates a surprisingly complicated civic problem: how do living and departed people share a home when everyone brings different routines, histories, needs, relationships, and boundaries?

The central policy is simple: No departed resident should be placed alone. Placements include at least two departed residents. Other departed people provide company; living relationships and an actively maintained home help anchor them to the present. Groups can include families. Children need guardian review and their own appropriate assent. Living households can take different forms, too.

The public registry currently contains nine departed residents and five example households.

Visitors can:

  • explore departed profiles and describe a living household

  • compare arrangements, hard exclusions, and unresolved questions

  • negotiate supported conflicts and review individual consent

  • prepare trial arrangements, exit plans, and relocation plans

  • save and revisit placement cases

Everyone involved gets a say. A refusal matters even when everything else about a placement looks promising.

Iona Vale, for example, repaired clocks in life. She would like to stop having to earn her place by fixing things. Orin Pell likes films and quiet company, but will not accept being recorded. Their histories give the experience personality. Their boundaries change what the application allows.

Demo

Visit Hearthafter. The public journey needs no login.

Quick demo: 81.8-second walkthrough · Interactive deep dive · Written deep-dive guide

A useful route through it:

  1. Meet Orin and read his camera-free boundary. Compare him and Iona with Noor’s household, where an indoor pet camera makes the arrangement unsuitable.

  2. Switch to June and Leila’s household. Read the quiet-hours proposal and see how accepting a supported change affects the comparison.

  3. Open a placement review. Work through the questions, individual decisions, trial plan, and relocation arrangements.

  4. Start a trial and revisit the saved case. Try withdrawing consent. Follow the relocation and handover through to the printable record.

  5. Try the Aster family with a household offering enough space. Guardian approval and Kit’s own assent are separate decisions.

  6. Open About for the registry relationships and the deeper explanation of source revisions, renewed review, App SDK work, and the staff workflow.

The source-change behavior is documented from a controlled authenticated test, described below.

Guest answers and cases stay in the browser. The authenticated staff desk is separate.

Code

Source code on GitHub.

Hearthafter uses Next.js 16, React 19, TypeScript, and Sanity, deployed through OpenNext on Cloudflare Workers.

The matching and placement rules are shared application logic. AI helped write, review, and test that code. A runtime language model does not decide who should live together.

My Build Process

Starting with something strange

Outside this challenge, we’ve been looking into using Sanity for a feature in a writing toolset we’re developing for writers. I considered going in that direction, but that project belongs to my work, and it’s a much more practical use case. Path Two specifically asked us to “Vibe-Code Something Strange,” so I wanted to lean into that.

I’m an experienced coder. In my usual work I write my own code, with occasional acceleration from AI. It was fun to sit down for this project and actually vibe-code something.

I used Codex on Windows, and OpenAI’s new dot feature as a coordinator between the Codex tasks. I directed the concept and reviewed the results. dot passed requirements between tasks, organized parallel implementation and testing, and brought the findings back together. The coding tasks handled implementation, debugging, and tests.

That division became part of the experiment. I could keep refining the fictional world and its rules while other tasks implemented features or tried to break them. It also exposed a weakness of vibe coding: an implementation can look completely reasonable while still misunderstanding what you meant. Catching those moments became a major part of the build.

Making a fictional civic service feel real

I wanted fictional characters with credible lives and specific needs. Using real deceased people would introduce a different set of problems, including how their families might feel about it. Creating the cast also meant the application could have examples designed to test genuinely different situations.

The presentation needed restraint. One of my prompts put it this way:

“It should feel strange because the premise is strange, not because the interface looks unserious.”

That affected the language, artwork, layout, and identity. I wanted a civic service responding to an extraordinary problem, with useful forms and believable procedures. Generic SaaS styling and Halloween decoration would both miss the point.

Making the schema carry the idea

Sanity holds the registry as linked content: departed profiles, histories, traits, boundaries, households, relationships, matching policy, and artwork.

Keeping those pieces separate matters. A boundary can be evaluated. A relationship can explain why two people might live well together. A household fact can rule an arrangement out. The profile’s prose gives those facts context.

One early dot-to-Codex implementation instruction was:

“hard exclusions first, explicit unknown required data, separate host-A/host-B/pair scores”

That wording comes from the earlier pair-based design. It translated the idea into checks: evaluate each person’s fit with the household, evaluate their relationship, and keep missing information visible. The later group refactor extended that reasoning across every selected resident and the relationships within the group.

Another instruction was:

“negotiations that genuinely resolve allowed conflicts”

That distinction mattered. An agreement needed to change something the participants could actually agree to, and the resulting comparison needed to reflect the change.

Orin’s camera boundary is a good example. Noor’s household can have plenty of other positives and still be excluded while its indoor recording conflicts with that boundary. A high average should not hide the problem.

June and Leila’s quiet-hours proposal is a different situation. There is an actual routine to negotiate: Iona can have morning company after 07:00, and Orin can finish his film and light by 22:00. Accepting that supported change recalculates the relevant preference factors. It does not grant a general override for hard boundaries.

One bug made that distinction especially clear. A quiet-hours agreement once made Iona look too compatible with Noor because the household could be quiet during the morning. But Iona wanted morning company, and Noor would be asleep. Quietness did not establish that anybody would actually be available. The correction kept the question unresolved instead of inventing a convenient new routine.

That was a useful check on the whole approach: an attractive explanation can still be wrong if the underlying facts do not support it.

The bugs that looked reasonable at first

The process included plenty of smaller corrections.

Saving an answer once caused that answer to disappear. The function clearing confirmations after a plan changed also cleared the newly saved information. The fix preserved the answer while resetting the consents that depended on the earlier version.

Questions for other residents sometimes retained the featured pair’s names. Choosing Sera and Tavi could still produce wording about Iona and Orin. The questions had to follow the selected residents, while the authored example remained its own content.

The introductory film could crash on its first frame because the browser’s animation timestamp preceded the stored start time. Clamping elapsed time fixed the scene lookup. Using the same clock for scene selection and motion also made Pause freeze both.

A browser test failure came from the harness itself: freezing time before a dynamic page finished loading prevented setup from completing. Moving that freeze after setup allowed the focused checks to run.

These came out of testing and review. The process was messier than a neat sequence of one prompt, one bug, and one perfect solution. The quoted excerpts in this post distinguish my prompts from dot’s implementation instructions to Codex.

Going past the Studio

I wanted Sanity’s contribution to show up in behavior.

App SDK

The custom staff desk uses the Sanity App SDK to work with live registry content and edit relationship guidance. An authenticated edit was saved, read back, and restored with a revision guard.

Workflows

Native Sanity Workflows adds a placement-readiness review tied to the exact placement revision. In an authenticated API exercise, attempting a trial without native approval was rejected. Once native approval was recorded, the trial audit referenced it. Replaying the approval did not create another transition.

The server still checks the answers, consent, plans, and permitted state changes. Workflow approval cannot make those requirements disappear.

When a source change invalidated approval

The most revealing experiment changed a real camera-policy record behind an active placement through an authenticated API test:

  • Saving a draft left published matching results unchanged.

  • Publishing the change excluded Orin and made an approved review stale.

  • Restoring the original facts restored compatibility.

  • Earlier consent and approval stayed invalid. They had to be renewed.

The test restored the source content and closed its private case afterward.

That sequence made the point better than a schema diagram could. Editing content can change a decision. Changing it back should not silently recreate permission someone gave under an earlier review.

The late correction that reached almost everywhere

The early implementation centered on pairs. That made sense as an example, but the example gradually hardened into the architecture.

My actual requirement was:

“The fundamental policy is: No departed resident should be placed alone.”

Two is the minimum. A family or another compatible group may be larger. I explicitly added:

“Do not solve this by merely replacing the words ‘two spirits’ with something vague.”

That meant changing schemas, types, matching, case records, consent, saved visits, and the interface. Every resident needed to remain visible. Every relevant relationship needed consideration. A third person could not disappear from a saved-case heading just because the first version rendered two names.

Keeping families together

Families brought another important check. Requiring a child’s guardian in the group was only half the rule. The application also had to avoid selecting the guardian while leaving their dependent child behind.

The expanded examples include an Aster family and households with different arrangements and capacities. The three-resident family journey was checked in the local preview and then against live Sanity content, including on mobile. Adult and guardian approvals alone did not start a trial. Kit’s separate assent was required. Reloading preserved all three residents and the five recorded decisions, and a partial-family selection was blocked.

Preserving old saved cases

The old saved data needed attention as well. Earlier household answers had no capacity field. Missing capacity was being treated as if it imposed no restriction, which could let a returning visitor start a new three-resident review without confirming space.

The fix keeps the old case readable while requiring explicit capacity for the new arrangement. That migration was tested on the deployed release using answers saved before the update. A separate three-adult journey was checked as well.

Asking for a harder final review

Near the end, I asked for three different audits:

  1. Design: Does every screen look finished and intentional?

  2. Blind-user UX: Can somebody with no knowledge of the project understand Hearthafter and how to use it?

  3. Sanity judge: Is the interesting Sanity work discoverable, with evidence a judge can inspect?

I also made the scope clear:

“Do not only audit the project and give me recommendations.”

I wanted high-confidence problems corrected and tested across the site.

One concrete example of the dot coordination came out of that pass. An independent QA task found a failure, dot relayed the reproduction and its consequences to the implementation task, and the retained case was checked again after deployment.

If the registry became unavailable during reload, a saved case still existed locally, but the error screen hid it. The visitor lost access to the relocation plan and withdrawal controls precisely when those should remain available.

The recovery view now preserves the saved names, decisions, and exit plan. It blocks new agreements and progress that require current content, while allowing withdrawal to be recorded locally. A retained case from the previous deployment was used to verify the behavior after the new release, including another reload.

My prompt also said, “Small, excellent touches are preferable to five unfinished new features.” Keeping that exit plan usable fit the priority. It tested whether the application followed its own promise that leaving is a supported outcome.

Getting the fiction and implementation to agree

The review also caught mistakes in how the project was being described.

One draft implied the service had existed for a century. Another line made it sound as though the living applicants needed homes. I corrected both: the program has just opened, the departed come from different eras, and the living households already have homes.

I also had to restore the pressure behind the service. The world is crowded with ghosts, and organization is needed to keep the peace. Without that, the site could drift into a pleasant matchmaking concept without explaining why this new institution exists.

The Overlap needed its causal chain spelled out. Data-center energy demand drives new generation and transmission infrastructure; the unforeseen effect follows from that expansion. Simply putting “data centers” near a ghost story left too much of the idea behind.

Those corrections led to a consistency pass across the homepage, world guide, film, profiles, matching explanations, forms, and records. One corrected paragraph was not enough if another screen still contradicted it.

Bringing the visual work back into line

The visual review changed the department identity more than once. The earlier mark looked church-like. I asked for a nonreligious civic identity, then rejected the doorway-based replacement when I saw it.

The approved version is a formal green, ivory, and gold seal with a human and ghost shaking hands in front of a home. That gives the mark something specific to say about the service. The iteration mattered; generating a replacement did not automatically mean the design was right.

The newsreel needed readable narrative inside the moving presentation. I requested actual HTML/CSS captions over the film, with a transcript, mobile treatment, and reduced-motion support. Text beneath the player did not meet that brief.

I kept the customer-facing experience separate from the technical explanation. As I put it in the prompt:

“Do not turn the main hero into a Sanity advertisement.”

Sanity’s role is available for people who want to inspect it. The homepage still has to make sense to someone trying to understand Hearthafter.

The final polish also covered basic deployment details such as canonical URLs and keeping private case and staff routes out of search. They belonged in the release pass, even with the deadline close.

Checking the release

Validation included 163 unit tests and 31 browser tests against the live Worker using Sanity content. The production build passed, and all 57 application content records passed Sanity validation without errors or warnings. These were automated checks. I also manually reviewed and tested the whole site. The unit and full browser runs preceded the final About-page and player presentation update. That update received focused live checks at 1440, 390, and 320 pixels, with no observed overflow, console errors, or violations in the sampled accessibility checks. The full 31-scenario browser suite was not rerun after that presentation change.

Independent checks also compared the nine published resident revisions returned by Sanity with the live application’s content endpoint. The deployed regression work included the family and three-adult journeys, old saved answers, a case retained from the earlier release, and deliberately blocked registry requests.

The testing covered more than a successful recommendation: unanswered questions, refusals, consent resets, stale source content, larger groups, guardian review, withdrawal, reloads, and registry failure.

The recorded automated staff checks cover authenticated API exercises and an App SDK edit/readback. They do not cover the complete authenticated staff edit-and-reload sequence on the final public hostname.

One implementation limit remains: placement and audit writes are atomic together, while editable source records are reread before the write rather than locked in that same transaction. The source-change checks described above work in the tested cases; they do not eliminate that race.

The build kept coming back to one question: did the code, content, and interface still mean the same thing? The useful prompts got specific about whose decision mattered, what was still unknown, and what must happen when circumstances changed.

Sanity Project Details

Project ID: o3jy1zm6

Dataset: production

The production dataset contains 56 fictional source documents: nine departed residents, five households, nine histories, nine traits, seventeen boundaries, six relationship records, and one matching policy. An artwork singleton brings the application-content total to 57, with three native image assets counted separately.

The live public content endpoint identifies its source as Sanity. The About registry explorer follows the actual references behind the profiles and recommendations.

Each placement review records the source revisions it used. Staff commands reread those sources before advancing a case; changes require renewed review. Native placement-readiness approval is attached to the corresponding placement revision.

Guest cases remain browser-local. Staff placements and audit records use private document paths and are excluded from public content queries. Staff mutations require a verified project administrator and use the predefined fictional households. Anonymous-read checks did not expose private placement, audit, or workflow records.

Hearthafter’s content model has to represent something messier than a compatibility score. People can change their minds. Facts can change. A promising arrangement can fall apart, a family may need to stay together, and a boundary can outweigh otherwise positive signals. Consent given under an earlier version of the facts may need to be renewed.

Giving those situations a real place in the schema is what made Hearthafter work as a service.

Top comments (0)