Add person-detail page with events timeline and relationships

New /trees/[id]/persons/[personId] view: life-events timeline with add/remove, and relationships grouped into parents/children/partners/siblings with an add form (kind + person picker + qualifier). People in the tree list now link here. Regenerated the OpenAPI client for the new endpoints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
This commit is contained in:
2026-06-06 12:10:56 -04:00
parent d6e2df4a61
commit 1f25eb2f21
4 changed files with 1531 additions and 5 deletions
+474
View File
@@ -210,10 +210,197 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/v1/trees/{tree_id}/persons/{person_id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get Person */
get: operations["get_person_api_v1_trees__tree_id__persons__person_id__get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/trees/{tree_id}/events": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Create Event */
post: operations["create_event_api_v1_trees__tree_id__events_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/trees/{tree_id}/persons/{person_id}/events": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** List Person Events */
get: operations["list_person_events_api_v1_trees__tree_id__persons__person_id__events_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/trees/{tree_id}/events/{event_id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
/** Delete Event */
delete: operations["delete_event_api_v1_trees__tree_id__events__event_id__delete"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/trees/{tree_id}/relationships": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Create Relationship */
post: operations["create_relationship_api_v1_trees__tree_id__relationships_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/trees/{tree_id}/persons/{person_id}/relationships": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** List Person Relationships */
get: operations["list_person_relationships_api_v1_trees__tree_id__persons__person_id__relationships_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/trees/{tree_id}/relationships/{relationship_id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
/** Delete Relationship */
delete: operations["delete_relationship_api_v1_trees__tree_id__relationships__relationship_id__delete"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
/** EventCreate */
EventCreate: {
/** Event Type */
event_type: string;
/** Person Id */
person_id?: string | null;
/** Relationship Id */
relationship_id?: string | null;
/** Place Id */
place_id?: string | null;
/** Date Value */
date_value?: string | null;
/** Date Start */
date_start?: string | null;
/** Date End */
date_end?: string | null;
/** Date Precision */
date_precision?: string | null;
/**
* Calendar
* @default gregorian
*/
calendar?: string;
/** Detail */
detail?: string | null;
/** Notes */
notes?: string | null;
};
/** EventRead */
EventRead: {
/**
* Id
* Format: uuid
*/
id: string;
/**
* Tree Id
* Format: uuid
*/
tree_id: string;
/** Event Type */
event_type: string;
/** Person Id */
person_id: string | null;
/** Relationship Id */
relationship_id: string | null;
/** Place Id */
place_id: string | null;
/** Date Value */
date_value: string | null;
/** Date Start */
date_start: string | null;
/** Date End */
date_end: string | null;
/** Date Precision */
date_precision: string | null;
/** Calendar */
calendar: string;
/** Detail */
detail: string | null;
/** Notes */
notes: string | null;
/**
* Created At
* Format: date-time
*/
created_at: string;
};
/** HTTPValidationError */
HTTPValidationError: {
/** Detail */
@@ -226,6 +413,13 @@ export interface components {
/** Password */
password: string;
};
/**
* ParentChildQualifier
* @description Qualifies a parent_child edge so adoption/donor/blended families are
* first-class rather than edge cases (ARCHITECTURE §5).
* @enum {string}
*/
ParentChildQualifier: "biological" | "adoptive" | "step" | "foster" | "donor" | "guardian";
/** PasswordResetConfirm */
PasswordResetConfirm: {
/** Token */
@@ -293,6 +487,60 @@ export interface components {
/** Display Name */
display_name?: string | null;
};
/** RelationshipCreate */
RelationshipCreate: {
type: components["schemas"]["RelationshipType"];
/**
* Person From Id
* Format: uuid
*/
person_from_id: string;
/**
* Person To Id
* Format: uuid
*/
person_to_id: string;
qualifier?: components["schemas"]["ParentChildQualifier"] | null;
/** Notes */
notes?: string | null;
};
/** RelationshipRead */
RelationshipRead: {
/**
* Id
* Format: uuid
*/
id: string;
/**
* Tree Id
* Format: uuid
*/
tree_id: string;
type: components["schemas"]["RelationshipType"];
/**
* Person From Id
* Format: uuid
*/
person_from_id: string;
/**
* Person To Id
* Format: uuid
*/
person_to_id: string;
qualifier: components["schemas"]["ParentChildQualifier"] | null;
/** Notes */
notes: string | null;
/**
* Created At
* Format: date-time
*/
created_at: string;
};
/**
* RelationshipType
* @enum {string}
*/
RelationshipType: "parent_child" | "partnership" | "sibling";
/** SessionRead */
SessionRead: {
user: components["schemas"]["UserRead"];
@@ -782,4 +1030,230 @@ export interface operations {
};
};
};
get_person_api_v1_trees__tree_id__persons__person_id__get: {
parameters: {
query?: never;
header?: never;
path: {
tree_id: string;
person_id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PersonRead"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
create_event_api_v1_trees__tree_id__events_post: {
parameters: {
query?: never;
header?: never;
path: {
tree_id: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["EventCreate"];
};
};
responses: {
/** @description Successful Response */
201: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["EventRead"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
list_person_events_api_v1_trees__tree_id__persons__person_id__events_get: {
parameters: {
query?: never;
header?: never;
path: {
tree_id: string;
person_id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["EventRead"][];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
delete_event_api_v1_trees__tree_id__events__event_id__delete: {
parameters: {
query?: never;
header?: never;
path: {
tree_id: string;
event_id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
create_relationship_api_v1_trees__tree_id__relationships_post: {
parameters: {
query?: never;
header?: never;
path: {
tree_id: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["RelationshipCreate"];
};
};
responses: {
/** @description Successful Response */
201: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["RelationshipRead"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
list_person_relationships_api_v1_trees__tree_id__persons__person_id__relationships_get: {
parameters: {
query?: never;
header?: never;
path: {
tree_id: string;
person_id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["RelationshipRead"][];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
delete_relationship_api_v1_trees__tree_id__relationships__relationship_id__delete: {
parameters: {
query?: never;
header?: never;
path: {
tree_id: string;
relationship_id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
}