Rebuild People as a family view (pedigree + family group); add recovery UI

The People page is no longer a flat list: it's a focus-person family view with a pedigree of ancestors (parents + grandparents), a spouse/partner panel, and a children panel — with inline 'add parent/child/spouse' (creates the person + the relationship), click-to-refocus, birth–death years, and a searchable people index. Modeled on how real genealogy tools center on a person and let you walk the graph.

Adds delete/restore UI: a Delete on the person page, per-tree delete + a 'Recently deleted' restore section on the trees list, and a Recovery page (sidebar) for deleted people.

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 22:19:01 -04:00
parent f2205b93f4
commit 22bc536978
7 changed files with 1002 additions and 106 deletions
+321 -25
View File
@@ -281,29 +281,6 @@
}
},
"/api/v1/trees": {
"get": {
"tags": [
"trees"
],
"summary": "List My Trees",
"operationId": "list_my_trees_api_v1_trees_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/TreeRead"
},
"type": "array",
"title": "Response List My Trees Api V1 Trees Get"
}
}
}
}
}
},
"post": {
"tags": [
"trees"
@@ -311,14 +288,14 @@
"summary": "Create Tree",
"operationId": "create_tree_api_v1_trees_post",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TreeCreate"
}
}
},
"required": true
}
},
"responses": {
"201": {
@@ -342,6 +319,51 @@
}
}
}
},
"get": {
"tags": [
"trees"
],
"summary": "List My Trees",
"operationId": "list_my_trees_api_v1_trees_get",
"parameters": [
{
"name": "deleted",
"in": "query",
"required": false,
"schema": {
"type": "boolean",
"default": false,
"title": "Deleted"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TreeRead"
},
"title": "Response List My Trees Api V1 Trees Get"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/trees/{tree_id}": {
@@ -385,6 +407,83 @@
}
}
}
},
"delete": {
"tags": [
"trees"
],
"summary": "Delete Tree",
"operationId": "delete_tree_api_v1_trees__tree_id__delete",
"parameters": [
{
"name": "tree_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Tree Id"
}
}
],
"responses": {
"204": {
"description": "Successful Response"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/trees/{tree_id}/restore": {
"post": {
"tags": [
"trees"
],
"summary": "Restore Tree",
"operationId": "restore_tree_api_v1_trees__tree_id__restore_post",
"parameters": [
{
"name": "tree_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Tree Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TreeRead"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/trees/{tree_id}/persons": {
@@ -455,6 +554,16 @@
"format": "uuid",
"title": "Tree Id"
}
},
{
"name": "deleted",
"in": "query",
"required": false,
"schema": {
"type": "boolean",
"default": false,
"title": "Deleted"
}
}
],
"responses": {
@@ -486,6 +595,50 @@
}
},
"/api/v1/trees/{tree_id}/persons/{person_id}": {
"delete": {
"tags": [
"persons"
],
"summary": "Delete Person",
"operationId": "delete_person_api_v1_trees__tree_id__persons__person_id__delete",
"parameters": [
{
"name": "tree_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Tree Id"
}
},
{
"name": "person_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Person Id"
}
}
],
"responses": {
"204": {
"description": "Successful Response"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
},
"get": {
"tags": [
"persons"
@@ -538,6 +691,59 @@
}
}
},
"/api/v1/trees/{tree_id}/persons/{person_id}/restore": {
"post": {
"tags": [
"persons"
],
"summary": "Restore Person",
"operationId": "restore_person_api_v1_trees__tree_id__persons__person_id__restore_post",
"parameters": [
{
"name": "tree_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Tree Id"
}
},
{
"name": "person_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Person Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PersonRead"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/trees/{tree_id}/events": {
"post": {
"tags": [
@@ -589,6 +795,51 @@
}
}
}
},
"get": {
"tags": [
"events"
],
"summary": "List Tree Events",
"operationId": "list_tree_events_api_v1_trees__tree_id__events_get",
"parameters": [
{
"name": "tree_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Tree Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EventRead"
},
"title": "Response List Tree Events Api V1 Trees Tree Id Events Get"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/trees/{tree_id}/persons/{person_id}/events": {
@@ -745,6 +996,51 @@
}
}
}
},
"get": {
"tags": [
"relationships"
],
"summary": "List Relationships",
"operationId": "list_relationships_api_v1_trees__tree_id__relationships_get",
"parameters": [
{
"name": "tree_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Tree Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/RelationshipRead"
},
"title": "Response List Relationships Api V1 Trees Tree Id Relationships Get"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/trees/{tree_id}/persons/{person_id}/relationships": {