Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/databuddy-internal/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ Read [codebase-map.md](./references/codebase-map.md) when you need deeper routin

### Database work

- Postgres schema: `packages/db/src/drizzle/schema.ts`
- Postgres schemas: `packages/db/src/drizzle/schema/` (`index.ts` barrel)
- Relations: `packages/db/src/drizzle/relations.ts`
- Drizzle client: `packages/db/src/client.ts`
- Production `DATABASE_URL` may already target PgBouncer; inspect both the process pool and PgBouncer queues before attributing API timeouts to PostgreSQL.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Use this file when the task spans multiple packages or when the right edit locat
- Postgres schema and relations
- ClickHouse client and schema
- Key files:
- [`packages/db/src/drizzle/schema.ts`](/Users/iza/Dev/Databuddy/packages/db/src/drizzle/schema.ts)
- [`packages/db/src/drizzle/schema/index.ts`](/Users/iza/Dev/Databuddy/packages/db/src/drizzle/schema/index.ts)
- [`packages/db/src/drizzle/relations.ts`](/Users/iza/Dev/Databuddy/packages/db/src/drizzle/relations.ts)
- [`packages/db/src/client.ts`](/Users/iza/Dev/Databuddy/packages/db/src/client.ts) — strips `sslrootcert=system` from `DATABASE_URL` before `pg` Pool: libpq uses it for the OS trust store, but node-postgres treats `sslrootcert` as a file path and throws `ENOENT` on path `"system"`.
- [`packages/db/src/clickhouse/client.ts`](/Users/iza/Dev/Databuddy/packages/db/src/clickhouse/client.ts)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
type BusinessContextSettings,
businessContextIsGenerating,
formatBusinessTeamContext,
formatBusinessMeasurementPlans,
businessMeasurementPlansSchema,
} from "@databuddy/shared/organization-business-context";
import { Button, Card, Field, Textarea, dayjs } from "@databuddy/ui";
import { Accordion, Dialog, DropdownMenu } from "@databuddy/ui/client";
Expand All @@ -25,6 +27,7 @@ import { useEffect, useRef, useState } from "react";
import { TopBar } from "@/components/layout/top-bar";
import { getUserFacingErrorMessage } from "@/lib/user-facing-error";
import { useBusinessContextDraft } from "./use-business-context-draft";
import { MeasurementPlanEditor } from "./measurement-plan-editor";

const emptyTeamContext: BusinessTeamContext = {
priority: "",
Expand Down Expand Up @@ -190,6 +193,8 @@ export function BusinessContextEditor({
const content = draft?.content ?? profile?.content ?? "";
const teamContext =
draft?.teamContext ?? profile?.teamContext ?? emptyTeamContext;
const measurementPlans =
draft?.measurementPlans ?? profile?.measurementPlans ?? [];
const generationWebsite = websites.find(
(site) =>
site.id === generation?.websiteId && site.domain === generation.domain
Expand All @@ -207,7 +212,9 @@ export function BusinessContextEditor({
(content.trim() !== (profile?.content ?? "") ||
Boolean(draftGeneration) ||
formatBusinessTeamContext(teamContext) !==
formatBusinessTeamContext(profile?.teamContext));
formatBusinessTeamContext(profile?.teamContext) ||
JSON.stringify(measurementPlans) !==
JSON.stringify(profile?.measurementPlans ?? []));
const conflict = dirty && draft.revision !== revision;
const activeGeneration = businessContextIsGenerating(settings);
const generating = isRequesting || activeGeneration;
Expand All @@ -233,11 +240,20 @@ export function BusinessContextEditor({
const teamTooLong = Object.values(teamContext).some(
(value) => value.trim().length > BUSINESS_CONTEXT_TEAM_FIELD_LIMIT
);
const plansValid =
businessMeasurementPlansSchema.safeParse(measurementPlans).success;
const bindingsValid = measurementPlans.every((plan) =>
websites.some(
(site) => site.id === plan.websiteId && site.domain === plan.domain
)
);
const saveDisabled =
!(ready && canEdit && dirty) ||
conflict ||
tooLong ||
teamTooLong ||
!plansValid ||
!bindingsValid ||
isSaving ||
review !== null;
const reviewedProfile = review?.kind === "history" ? review.profile : profile;
Expand All @@ -247,6 +263,10 @@ export function BusinessContextEditor({
: (reviewedProfile?.content ?? "");
const reviewTeam =
review?.kind === "generation" ? teamContext : reviewedProfile?.teamContext;
const reviewPlans =
review?.kind === "generation"
? measurementPlans
: reviewedProfile?.measurementPlans;

useEffect(() => {
if (
Expand All @@ -265,6 +285,7 @@ export function BusinessContextEditor({
revision,
generationId: readyGeneration.id,
teamContext: profile?.teamContext,
measurementPlans: profile?.measurementPlans,
});
}, [
ready,
Expand All @@ -274,6 +295,7 @@ export function BusinessContextEditor({
readyGeneration,
revision,
profile?.teamContext,
profile?.measurementPlans,
setDraft,
]);

Expand Down Expand Up @@ -333,6 +355,7 @@ export function BusinessContextEditor({
content: content.trim(),
revision: draft.revision,
teamContext,
measurementPlans,
...(draftGeneration ? { generationId: draftGeneration.id } : {}),
}),
"Changes saved"
Expand Down Expand Up @@ -676,6 +699,30 @@ export function BusinessContextEditor({
)}
</Card.Footer>
</Card>
<MeasurementPlanEditor
disabled={!(ready && canEdit) || isSaving}
websites={websites}
plans={measurementPlans}
onChange={(plans) => {
setDraft({
...(draft ?? { content, revision, teamContext }),
measurementPlans: plans,
});
setNotice("");
}}
/>
{dirty && !plansValid && (
<p className="text-destructive text-xs" role="alert">
Complete the outcome name and both event names before saving. Event
names and namespace can contain up to 256 characters.
</p>
)}
{dirty && !bindingsValid && (
<p className="text-destructive text-xs" role="alert">
Update or remove definitions for changed or unavailable websites
before saving.
</p>
)}
<Sources
sources={draftGeneration?.draft?.sources ?? profile?.sources ?? []}
/>
Expand Down Expand Up @@ -706,16 +753,24 @@ export function BusinessContextEditor({
{review?.kind === "generation"
? "Using this draft replaces your local text. You can edit it before saving."
: review?.kind === "history"
? "Restoring replaces the saved brief and your current edits. Your current saved version stays in history."
? "Restoring replaces the saved brief, team context, event definitions, and your current edits. Your current saved version stays in history."
: "Your edits are still in the editor. Choose which version to keep working on."}
</Dialog.Description>
</Dialog.Header>
<Dialog.Body className="max-h-[60vh] space-y-4 overflow-y-auto">
<BriefChanges
before={[content, formatBusinessTeamContext(teamContext)]
before={[
content,
formatBusinessTeamContext(teamContext),
formatBusinessMeasurementPlans(measurementPlans),
]
.filter(Boolean)
.join("\n\n")}
after={[reviewText, formatBusinessTeamContext(reviewTeam)]
after={[
reviewText,
formatBusinessTeamContext(reviewTeam),
formatBusinessMeasurementPlans(reviewPlans),
]
.filter(Boolean)
.join("\n\n")}
/>
Expand Down Expand Up @@ -788,6 +843,7 @@ export function BusinessContextEditor({
revision,
generationId: pendingDraft.id,
teamContext,
measurementPlans,
});
setReview(null);
editorRef.current?.focus();
Expand Down
Loading
Loading