/**
* API types — hand-written mirror of the server Pydantic schemas (server/app/schemas.py).
* Kept in sync manually; openapi-typescript generation is available via
* `npm run generate-api-types --workspace @ltt/shared` once the server is running.
*/
export interface User {
id: string;
email: string;
nickname: string;
created_at: string;
}
export interface TokenOut {
token: string;
expires_at: string;
}
export interface Project {
id: string;
name: string;
description: string | null;
share_token: string;
created_at: string;
}
export interface ProjectDetail extends Project {
owner_nickname: string;
report_counts: { total: number; open: number };
}
export interface Author {
id: string;
nickname: string;
}
export interface Attachment {
id: string;
file_id: string;
kind: string;
filename: string;
mime: string;
size: number;
annotation_shapes: AnnotationShape[] | null;
}
export interface AnnotationShape {
tool: "pen" | "arrow" | "rect" | "text";
color: string;
strokeWidth: number;
points?: [number, number][];
rect?: [number, number, number, number];
pos?: [number, number];
text?: string;
}
export interface RecordingStep {
id: string;
step_index: number;
type: "click" | "input" | "url_change" | "navigation" | "note" | "screenshot" | "console";
offset_ms: number;
data: Record<string, unknown>;
screenshot_attachment_id: string | null;
}
export interface MouseTrack {
/** viewport the points were captured in, CSS px */
viewport: { w: number; h: number };
/** cursor samples; t = ms since recording start, x/y viewport-relative */
points: { t: number; x: number; y: number }[];
}
export interface ElementContext {
selector?: string | null;
unique_selector?: string | null;
tag?: string | null;
id?: string | null;
classes?: string[] | null;
text_snippet?: string | null;
aria_label?: string | null;
test_attributes?: Record<string, string> | null;
rect?: { x: number; y: number; w: number; h: number } | null;
screenshot_rel?: { x: number; y: number; w: number; h: number } | null;
extra?: Record<string, unknown> | null;
}
export interface Report {
id: string;
type: "element_note" | "recording";
title: string;
description: string | null;
status: "open" | "fixed" | "wont_fix";
page_url: string | null;
page_title: string | null;
environment: Record<string, unknown>;
element: ElementContext | null;
mouse_track: MouseTrack | null;
share_token: string;
created_at: string;
updated_at: string;
}
export interface ReportListItem extends Report {
author: Author;
}
export interface ReportDetail extends ReportListItem {
attachments: Attachment[];
steps: RecordingStep[];
/** present on the detail GET — lets the panel link back to the project */
project_share_token?: string | null;
}
export interface ReportList {
items: ReportListItem[];
total: number;
limit: number;
offset: number;
}
export interface UploadResult {
file_id: string;
mime: string;
size: number;
filename: string;
}
// request payloads
export interface StepIn {
type: RecordingStep["type"];
offset_ms: number;
data?: Record<string, unknown>;
attachment_id?: string | null;
}
export interface ReportCreateIn {
project_id: string;
type: Report["type"];
title: string;
description?: string | null;
page_url?: string | null;
page_title?: string | null;
environment?: Record<string, unknown> | null;
element?: ElementContext | null;
mouse_track?: MouseTrack | null;
attachment_ids?: string[] | null;
annotation_shapes?: Record<string, AnnotationShape[]> | null;
steps?: StepIn[] | null;
}
export interface ReportUpdateIn {
title?: string;
description?: string | null;
status?: Report["status"];
}