Read through all of these instructions in order to understand the architecture of this Val, and implementation details important to this Val, and then once you have it all in your memory you should get to work.
If anything is unclear, you should stop and ask me for clarification before proceeding. Do not write code if you are unsure of what is expected.
Important: there are checkpoints in these instructions where you are directed to stop and ask permission before proceeding. Follow those instructions and do not proceed without permission.
This Val follows a strict layered architecture. Understanding these layers is critical before writing any code:
Services (/backend/services/):
Controllers (/backend/controllers/):
Routes (/backend/routes/):
We use a consistent two-layer response handling strategy:
Layer 1: HTTP Transport Layer (response.ok)
Layer 2: Service Business Logic (result.success)
Create this directory structure with README.md files:
Each route subdirectory should have a _[directory_name].routes.ts file that mounts routes for that section.
Townie, stop here! Ask for permission to proceed.
main.tsx Set up a Hono app like this:
import { Hono } from "npm:hono@3.12.12";
// Import route modules
import apiRoutes from "./backend/routes/api/\_api.routes.ts";
import taskRoutes from "./backend/routes/tasks/\_tasks.routes.ts";
import viewRoutes from "./backend/routes/views/\_views.routes.ts";
const app = new Hono();
// Unwrap Hono errors to see original error details
app.onError((err, c) => {
throw err;
});
// Mount route modules
app.route("/api", apiRoutes);
app.route("/tasks", taskRoutes);
app.route("/views", viewRoutes);
export default app.fetch;
Route Module Template Each _[directory].routes.ts file should follow this pattern:
import { Hono } from "npm:hono@3.12.12";
const app = new Hono();
// TODO: Add routes here
export default app;
Townie, stop here! Ask for permission to proceed.
Create /backend/services/notion.service.ts with ONLY direct Notion API calls:
Notion Service Requirements:
import { Client } from "npm:@notionhq/client";
const notion = new Client({ auth: Deno.env.get("NOTION_API_KEY") });
getDatabases()
- search for all databasesgetDatabaseById(databaseId: string)
- retrieve specific databaseService Function Pattern:
export async function getDatabaseById(databaseId: string) {
try {
const response = await notion.databases.retrieve({
database_id: databaseId,
});
return {
success: true,
data: response,
timestamp: new Date().toISOString(),
};
} catch (error) {
return {
success: false,
error: error.message,
timestamp: new Date().toISOString(),
};
}
}
Important: Services should NOT handle environment variables for database IDs or orchestrate multiple calls. That's controller responsibility.
Townie, stop here! Ask for permission to proceed.
Create /backend/controllers/health.controller.ts that orchestrates services:
Controller Requirements:
import { getDatabaseById } from "../services/notion.service.ts";
(GLANCE_DEMOS_DB_ID, GLANCE_CONTENT_DB_ID, GLANCE_INTERACTIONS_DB_ID)
Required Controller Functions:
getConfiguredDatabases()
- Check all configured databases using environment variablesgetHealthStatus()
- Generate formatted health response for APIController Function Pattern:
export async function getConfiguredDatabases() {
const demosDbId = Deno.env.get("GLANCE_DEMOS_DB_ID");
// ... get other DB IDs
const databases = { demos: null, content: null, interactions: null };
const errors: string[] = [];
// For each configured database:
if (demosDbId) {
const result = await getDatabaseById(demosDbId); // Call service
if (result.success) {
databases.demos = result.data;
} else {
errors.push(`Demos DB: ${result.error}`);
}
} else {
errors.push("GLANCE_DEMOS_DB_ID not configured");
}
// ... repeat for other databases
return {
success: errors.length === 0,
data: databases,
errors: errors.length > 0 ? errors : undefined,
timestamp: new Date().toISOString(),
};
}
Townie, stop here! Ask for permission to proceed.
Add Health Endpoint
In /backend/routes/api/_api.routes.ts:
import { Hono } from "npm:hono@3.12.12";
import { getHealthStatus } from "../../controllers/health.controller.ts";
const app = new Hono();
app.get("/health", async (c) => {
const healthStatus = await getHealthStatus();
return c.json(healthStatus);
});
export default app;
Add Root Route
In main.tsx, add a root route:
// Add this before mounting route modules
app.get("/", async (c) => {
return c.html(`<h1>Stay tuned...</h1>`);
});
Townie, stop here! Ask for permission to proceed.
Before proceeding, verify your implementation follows the architecture:
The health endpoint should return:
{
"status": "ok",
"timestamp": "2025-07-14T18:26:28.270Z",
"service": "glance-demos",
"databases": {
"configured": true,
"demos": "connected",
"content": "connected",
"interactions": "connected"
}
}
This architecture is intended to create clear separation of concerns and should make the codebase maintainable and testable.
Townie, stop here! Before proceeding to additional steps, confirm that this step is working correctly.
If there was an error regarding environment variables: remind the user to upload vars.env
to the Environment Variables section of this Val, and to then let you know when that step is complete so you can retry.
If everything is working as intended: conclude this step with these messages:
Tell the user that the next step is to add authentication to the web routes of this Val, and that they should copy this line and paste it into Townie:
Add auth to this Val with what you see in
/_townie/02-auth.md