Build Cobble project structure from the handoff

- Remove the preset document suite (letters, emails, invoices, quotes,
  PDF generation, line items) with its routes, API handlers and tables
- Rebrand to Cobble: site data, Lato, handoff design tokens (light and
  dark) in main.css, brand assets in public/brand, ember stone logo and
  regenerated favicons
- Add bots, bot_messages, bot_tasks and bot_task_steps tables, regenerate
  DB types (BUN_SQLITE_COBBLE_*) and add a sample crew seed (seed:bots)
- Add the chat shell at /admin/bots: rail with pinned coordinator and
  user popover, bottom-anchored conversation with composer, working-on
  panel, and a send-message API scoped to the session user
- Add BotStone and Wordmark components, crew status and relative time
  utils with tests, and update AGENTS.md

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-18 17:10:36 +01:00
co-authored by Claude Opus 5.5
parent 81e4a8aecc
commit 3286b80029
340 changed files with 3161 additions and 12060 deletions
@@ -1,8 +1,8 @@
import { describe, expect, test } from "bun:test";
import checkUserAccess from "./check-user-access";
import type { BUN_SQLITE_BSQLITE_USER_TYPES } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_USER_TYPES } from "@/db/types/db";
function makeUserTypes(...types: string[]): BUN_SQLITE_BSQLITE_USER_TYPES[] {
function makeUserTypes(...types: string[]): BUN_SQLITE_COBBLE_USER_TYPES[] {
return types.map((user_type) => ({
user_id: 1,
user_type,
@@ -1,11 +1,11 @@
import type { BUN_SQLITE_BSQLITE_USER_TYPES } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_USER_TYPES } from "@/db/types/db";
import type { UserType } from "@/src/types";
import type { APIResponseObject } from "@moduletrace/bunext/types";
type Params = {
includes?: UserType[];
excludes?: UserType[];
user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
user_types?: BUN_SQLITE_COBBLE_USER_TYPES[] | null;
};
export default function checkUserAccess({
@@ -1,7 +1,7 @@
import BunSQLite from "@moduletrace/bun-sqlite";
import type {
BUN_SQLITE_BSQLITE_USERS,
BUN_SQLITE_BSQLITE_USER_TYPES,
BUN_SQLITE_COBBLE_USERS,
BUN_SQLITE_COBBLE_USER_TYPES,
BunSQLiteTables,
} from "@/db/types/db";
import AppError from "@/src/utils/app-error";
@@ -24,7 +24,7 @@ export default async function createSignupUser({
username,
}: Params) {
const new_user_res = await BunSQLite.insert<
BUN_SQLITE_BSQLITE_USERS,
BUN_SQLITE_COBBLE_USERS,
(typeof BunSQLiteTables)[number]
>({
table: "users",
@@ -50,7 +50,7 @@ export default async function createSignupUser({
}
const type_res = await BunSQLite.insert<
BUN_SQLITE_BSQLITE_USER_TYPES,
BUN_SQLITE_COBBLE_USER_TYPES,
(typeof BunSQLiteTables)[number]
>({
table: "user_types",
@@ -1,6 +1,6 @@
import BunSQLite from "@moduletrace/bun-sqlite";
import type {
BUN_SQLITE_BSQLITE_SSO_LOGIN_CODES,
BUN_SQLITE_COBBLE_SSO_LOGIN_CODES,
BunSQLiteTables,
} from "@/db/types/db";
@@ -10,7 +10,7 @@ type Params = {
export default async function deleteUserSSOCodes({ user_id }: Params) {
return await BunSQLite.delete<
BUN_SQLITE_BSQLITE_SSO_LOGIN_CODES,
BUN_SQLITE_COBBLE_SSO_LOGIN_CODES,
(typeof BunSQLiteTables)[number]
>({
table: "sso_login_codes",
@@ -1,5 +1,5 @@
import BunSQLite from "@moduletrace/bun-sqlite";
import type { BUN_SQLITE_BSQLITE_USERS, BunSQLiteTables } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_USERS, BunSQLiteTables } from "@/db/types/db";
type Params = {
identifier: string;
@@ -11,7 +11,7 @@ type Params = {
export default async function findUserByIdentifier({ identifier }: Params) {
return (
await BunSQLite.select<
BUN_SQLITE_BSQLITE_USERS,
BUN_SQLITE_COBBLE_USERS,
(typeof BunSQLiteTables)[number]
>({
table: "users",
@@ -1,6 +1,6 @@
import { randomInt } from "node:crypto";
import BunSQLite from "@moduletrace/bun-sqlite";
import type { BUN_SQLITE_BSQLITE_USERS, BunSQLiteTables } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_USERS, BunSQLiteTables } from "@/db/types/db";
import AppError from "@/src/utils/app-error";
type Params = {
@@ -24,7 +24,7 @@ export default async function generateUniqueUsername({ email }: Params) {
const existing = (
await BunSQLite.select<
BUN_SQLITE_BSQLITE_USERS,
BUN_SQLITE_COBBLE_USERS,
(typeof BunSQLiteTables)[number]
>({
table: "users",
@@ -1,5 +1,5 @@
import type {
BUN_SQLITE_BSQLITE_USER_TYPES,
BUN_SQLITE_COBBLE_USER_TYPES,
BunSQLiteTables,
} from "@/db/types/db";
import type { UserAuthReturn } from "@/src/types";
@@ -16,7 +16,7 @@ export default async function grabUserAuthTypes({
const user_types =
(
await BunSQLite.select<
BUN_SQLITE_BSQLITE_USER_TYPES,
BUN_SQLITE_COBBLE_USER_TYPES,
(typeof BunSQLiteTables)[number]
>({
table: "user_types",
@@ -1,6 +1,6 @@
import BunSQLite from "@moduletrace/bun-sqlite";
import type {
BUN_SQLITE_BSQLITE_USER_PASSWORDS,
BUN_SQLITE_COBBLE_USER_PASSWORDS,
BunSQLiteTables,
} from "@/db/types/db";
@@ -15,7 +15,7 @@ type Params = {
export default async function grabUserPasswordHash({ user_id }: Params) {
const record = (
await BunSQLite.select<
BUN_SQLITE_BSQLITE_USER_PASSWORDS,
BUN_SQLITE_COBBLE_USER_PASSWORDS,
(typeof BunSQLiteTables)[number]
>({
table: "user_passwords",
@@ -13,7 +13,7 @@ import type {
BunextAPIRouteHandlerObjectReturn,
} from "@moduletrace/bunext/types";
import type {
BUN_SQLITE_BSQLITE_SSO_LOGIN_CODES,
BUN_SQLITE_COBBLE_SSO_LOGIN_CODES,
BunSQLiteTables,
} from "@/db/types/db";
@@ -37,7 +37,7 @@ export default async function issueSSOLoginCode({
const sso_code = randomInt(0, 1000000).toString().padStart(6, "0");
const record_sso = await BunSQLite.insert<
BUN_SQLITE_BSQLITE_SSO_LOGIN_CODES,
BUN_SQLITE_COBBLE_SSO_LOGIN_CODES,
(typeof BunSQLiteTables)[number]
>({
data: [
+4 -4
View File
@@ -4,8 +4,8 @@ import { encrypt } from "@/src/utils/crypt";
import grabUsers from "../db/users/grab-users";
import BunSQLite from "@moduletrace/bun-sqlite";
import type {
BUN_SQLITE_BSQLITE_MEDIA,
BUN_SQLITE_BSQLITE_USER_TYPES,
BUN_SQLITE_COBBLE_MEDIA,
BUN_SQLITE_COBBLE_USER_TYPES,
BunSQLiteTables,
} from "@/db/types/db";
import generateCSRF from "./gen-csrf";
@@ -44,7 +44,7 @@ export default async function loginUser({
const target_user_types = (
await BunSQLite.select<
BUN_SQLITE_BSQLITE_USER_TYPES,
BUN_SQLITE_COBBLE_USER_TYPES,
(typeof BunSQLiteTables)[number]
>({
table: "user_types",
@@ -65,7 +65,7 @@ export default async function loginUser({
const profile_media = target_user.profile_media_id
? (
await BunSQLite.select<
BUN_SQLITE_BSQLITE_MEDIA,
BUN_SQLITE_COBBLE_MEDIA,
(typeof BunSQLiteTables)[number]
>({
table: "media",
@@ -1,6 +1,6 @@
import BunSQLite from "@moduletrace/bun-sqlite";
import type {
BUN_SQLITE_BSQLITE_USER_PASSWORDS,
BUN_SQLITE_COBBLE_USER_PASSWORDS,
BunSQLiteTables,
} from "@/db/types/db";
import hashPassword from "./hash-password";
@@ -21,7 +21,7 @@ export default async function setUserPassword({ user_id, password }: Params) {
const password_hash = await hashPassword({ password });
const res = await BunSQLite.insert<
BUN_SQLITE_BSQLITE_USER_PASSWORDS,
BUN_SQLITE_COBBLE_USER_PASSWORDS,
(typeof BunSQLiteTables)[number]
>({
table: "user_passwords",
@@ -1,5 +1,5 @@
import type {
BUN_SQLITE_BSQLITE_SSO_LOGIN_CODES,
BUN_SQLITE_COBBLE_SSO_LOGIN_CODES,
BunSQLiteTables,
} from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
@@ -70,7 +70,7 @@ export default async function verifySSOCode({
const target_sso = (
await BunSQLite.select<
BUN_SQLITE_BSQLITE_SSO_LOGIN_CODES,
BUN_SQLITE_COBBLE_SSO_LOGIN_CODES,
(typeof BunSQLiteTables)[number]
>({
table: "sso_login_codes",