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
@@ -7,7 +7,7 @@ import { AppContext } from "@/src/pages/__root";
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
import EJSON from "@/src/utils/ejson";
import { useUsersGet } from "@/src/hooks/use-users-get";
import type { BUN_SQLITE_BSQLITE_USERS_JOIN } from "@/src/types/sql-joins";
import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
type Props = {
users_type?: UserType | null;
@@ -49,7 +49,7 @@ export default function useAllPeopleListInit({ users_type }: Props) {
query: location_query,
});
const selected = useRef(new Set<BUN_SQLITE_BSQLITE_USERS_JOIN>());
const selected = useRef(new Set<BUN_SQLITE_COBBLE_USERS_JOIN>());
const { refresh, setRefresh } = useStatus();
@@ -5,7 +5,7 @@ import { useRef } from "react";
import _ from "lodash";
import twuiSlugToNormalText from "@moduletrace/twui/utils/slug-to-normal-text";
import type useAllPeopleListInit from "../(hooks)/use-all-people-list-init";
import type { BUN_SQLITE_BSQLITE_USERS_JOIN } from "@/src/types/sql-joins";
import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
import searchableFieldToQueryField from "@/src/utils/searchable-field-to-query-field";
import type { ApiReqParams } from "@/src/types";
@@ -13,7 +13,7 @@ export default function AllPeopleListSearch({
setUsersQuery,
location_query,
}: ReturnType<typeof useAllPeopleListInit>) {
const searchable_fields: (keyof BUN_SQLITE_BSQLITE_USERS_JOIN)[] = [
const searchable_fields: (keyof BUN_SQLITE_COBBLE_USERS_JOIN)[] = [
"first_name",
"last_name",
"email",
@@ -3,10 +3,10 @@ import _ from "lodash";
import Span from "@moduletrace/twui/layout/Span";
import Card from "@moduletrace/twui/elements/Card";
import type { RefObject } from "react";
import type { BUN_SQLITE_BSQLITE_USERS_JOIN } from "@/src/types/sql-joins";
import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
type Props = {
selected: RefObject<Set<BUN_SQLITE_BSQLITE_USERS_JOIN>>;
selected: RefObject<Set<BUN_SQLITE_COBBLE_USERS_JOIN>>;
};
export default function AllPeopleListSelections({ selected }: Props) {
@@ -8,14 +8,14 @@ import Row from "@moduletrace/twui/layout/Row";
import Span from "@moduletrace/twui/layout/Span";
import Stack from "@moduletrace/twui/layout/Stack";
import grabMediaURL from "@/src/functions/backend/media/grab-media-url";
import type { BUN_SQLITE_BSQLITE_USERS_JOIN } from "@/src/types/sql-joins";
import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
import { AppContext } from "@/src/pages/__root";
import isReadOnlyUser from "@/src/utils/rbac/is-read-only-user";
import { useContext } from "react";
import dayjs from "dayjs";
type Props = {
person: BUN_SQLITE_BSQLITE_USERS_JOIN;
person: BUN_SQLITE_COBBLE_USERS_JOIN;
};
export default function PeopleListCard({ person }: Props) {
@@ -5,7 +5,7 @@ import { useContext } from "react";
import UserForm from "../../../create/(partials)/user-form";
import _ from "lodash";
import type { UserFormObject, UserType } from "@/src/types";
import type { BUN_SQLITE_BSQLITE_USERS_JOIN } from "@/src/types/sql-joins";
import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
export default function EditPersonFormSection() {
const { pageProps } = useContext(AppContext);
@@ -21,8 +21,8 @@ export default function EditPersonFormSection() {
<UserForm
existing_user={{
...(_.pick<
BUN_SQLITE_BSQLITE_USERS_JOIN,
keyof BUN_SQLITE_BSQLITE_USERS_JOIN
BUN_SQLITE_COBBLE_USERS_JOIN,
keyof BUN_SQLITE_COBBLE_USERS_JOIN
>(person, [
"first_name",
"last_name",
@@ -1,6 +1,6 @@
import type {
BUN_SQLITE_BSQLITE_USER_TYPES,
BUN_SQLITE_BSQLITE_USERS,
BUN_SQLITE_COBBLE_USER_TYPES,
BUN_SQLITE_COBBLE_USERS,
} from "@/db/types/db";
import adminCrudHandler from "@/src/functions/frontend/admin-crud-handler";
import clientDeleteMediaHandler from "@/src/functions/frontend/client-delete-media-handler";
@@ -26,7 +26,7 @@ export default async function submitUserForm({
return;
}
const new_user_data: BUN_SQLITE_BSQLITE_USERS = _.pick<
const new_user_data: BUN_SQLITE_COBBLE_USERS = _.pick<
UserFormObject,
keyof UserFormObject
>(form, ["first_name", "last_name", "email", "username", "bio"]);
@@ -99,7 +99,7 @@ export default async function submitUserForm({
if (user_types?.[0]) {
const add_user_types =
await adminCrudHandler<BUN_SQLITE_BSQLITE_USER_TYPES>({
await adminCrudHandler<BUN_SQLITE_COBBLE_USER_TYPES>({
action: "insert",
table: "user_types",
insert_data: user_types.map((ut) => ({
@@ -1,7 +1,7 @@
import Form from "@moduletrace/twui/form/Form";
import LoadingOverlay from "@moduletrace/twui/elements/LoadingOverlay";
import useFormInit from "@/src/hooks/use-form-init";
import type { BUN_SQLITE_BSQLITE_USERS } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_USERS } from "@/db/types/db";
import submitUserForm from "../../(functions)/submit-user-form";
import UserFormName from "./user-form-name";
import Tag from "@moduletrace/twui/elements/Tag";
@@ -16,7 +16,7 @@ import UserFormImage from "./user-form-image";
type Props = {
existing_user?: UserFormObject;
existing_user_full?: BUN_SQLITE_BSQLITE_USERS;
existing_user_full?: BUN_SQLITE_COBBLE_USERS;
};
export default function UserForm({ existing_user, existing_user_full }: Props) {
@@ -1,4 +1,4 @@
import type { BUN_SQLITE_BSQLITE_USERS } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_USERS } from "@/db/types/db";
import Button from "@moduletrace/twui/layout/Button";
import useFormInit from "@/src/hooks/use-form-init";
@@ -6,7 +6,7 @@ export default function UserFormAction({
setForm,
existing,
existing_full,
}: ReturnType<typeof useFormInit<BUN_SQLITE_BSQLITE_USERS>>) {
}: ReturnType<typeof useFormInit<BUN_SQLITE_COBBLE_USERS>>) {
const title = existing_full?.id ? "Update User" : "Create New User";
return (
@@ -1,4 +1,4 @@
import type { BUN_SQLITE_BSQLITE_USERS } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_USERS } from "@/db/types/db";
import TinyMCEEditor from "@moduletrace/twui/editors/TinyMCE";
import Button from "@moduletrace/twui/layout/Button";
import useFormInit from "@/src/hooks/use-form-init";
@@ -8,7 +8,7 @@ export default function UserFormBio({
existing,
existing_full,
form,
}: ReturnType<typeof useFormInit<BUN_SQLITE_BSQLITE_USERS>>) {
}: ReturnType<typeof useFormInit<BUN_SQLITE_COBBLE_USERS>>) {
const title = existing_full?.id ? "Update User" : "Create New User";
return (
@@ -1,11 +1,11 @@
import type { BUN_SQLITE_BSQLITE_USERS } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_USERS } from "@/db/types/db";
import Input from "@moduletrace/twui/form/Input";
import useFormInit from "@/src/hooks/use-form-init";
export default function UserFormEmail({
setForm,
form,
}: ReturnType<typeof useFormInit<BUN_SQLITE_BSQLITE_USERS>>) {
}: ReturnType<typeof useFormInit<BUN_SQLITE_COBBLE_USERS>>) {
return (
<>
<Input
@@ -2,7 +2,7 @@ import ImageUpload from "@moduletrace/twui/form/ImageUpload";
import grabMediaURL from "@/src/functions/backend/media/grab-media-url";
import useFormInit from "@/src/hooks/use-form-init";
import type { UserFormObject } from "@/src/types";
import type { BUN_SQLITE_BSQLITE_USERS_JOIN } from "@/src/types/sql-joins";
import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
export default function UserFormImage({
setForm,
@@ -11,7 +11,7 @@ export default function UserFormImage({
}: ReturnType<typeof useFormInit<UserFormObject>>) {
const title = existing_full?.id ? "Update User" : "Create New User";
const person = existing_full as BUN_SQLITE_BSQLITE_USERS_JOIN | undefined;
const person = existing_full as BUN_SQLITE_COBBLE_USERS_JOIN | undefined;
const user_image_url = person?.profile_media_id
? grabMediaURL({
@@ -1,4 +1,4 @@
import type { BUN_SQLITE_BSQLITE_USERS } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_USERS } from "@/db/types/db";
import Input from "@moduletrace/twui/form/Input";
import Row from "@moduletrace/twui/layout/Row";
import useFormInit from "@/src/hooks/use-form-init";
@@ -6,7 +6,7 @@ import useFormInit from "@/src/hooks/use-form-init";
export default function UserFormName({
setForm,
form,
}: ReturnType<typeof useFormInit<BUN_SQLITE_BSQLITE_USERS>>) {
}: ReturnType<typeof useFormInit<BUN_SQLITE_COBBLE_USERS>>) {
return (
<Row className="w-full grid xl:grid-cols-2">
<Input
@@ -1,11 +1,11 @@
import type { BUN_SQLITE_BSQLITE_USERS } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_USERS } from "@/db/types/db";
import Input from "@moduletrace/twui/form/Input";
import useFormInit from "@/src/hooks/use-form-init";
export default function UserFormUsername({
setForm,
form,
}: ReturnType<typeof useFormInit<BUN_SQLITE_BSQLITE_USERS>>) {
}: ReturnType<typeof useFormInit<BUN_SQLITE_COBBLE_USERS>>) {
return (
<>
<Input