Exosphere is a set of small, modular, self-hostable community tools built on the AT Protocol. app.exosphere.site
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: status types

Hugo 0166a9fd 8c41ca05

+1838 -58
+7
drizzle/0008_kanban_status_type.sql
··· 1 + ALTER TABLE `kanban_columns` ADD `status_type` text DEFAULT 'backlog' NOT NULL; 2 + --> statement-breakpoint 3 + UPDATE `kanban_columns` SET `status_type` = 'planned' WHERE `slug` = 'todo'; 4 + --> statement-breakpoint 5 + UPDATE `kanban_columns` SET `status_type` = 'started' WHERE `slug` = 'in-progress'; 6 + --> statement-breakpoint 7 + UPDATE `kanban_columns` SET `status_type` = 'completed' WHERE `slug` = 'done';
+11
drizzle/0009_damp_microbe.sql
··· 1 + ALTER TABLE `feature_requests` RENAME COLUMN "label_tid" TO "label_updated_at";--> statement-breakpoint 2 + ALTER TABLE `kanban_tasks` RENAME COLUMN "label_tid" TO "label_updated_at";--> statement-breakpoint 3 + CREATE TABLE `label_pds_records` ( 4 + `entity_id` text NOT NULL, 5 + `entity_type` text NOT NULL, 6 + `actor_did` text NOT NULL, 7 + `rkey` text NOT NULL, 8 + PRIMARY KEY(`entity_id`, `entity_type`, `actor_did`) 9 + ); 10 + --> statement-breakpoint 11 + ALTER TABLE `kanban_columns` ADD `status_type` text DEFAULT 'backlog' NOT NULL;
+1581
drizzle/meta/0009_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "e3c5292c-aa87-4b07-8df2-08ed9e38c774", 5 + "prevId": "eec14d3f-541d-45c5-844c-4b45eb164b0c", 6 + "tables": { 7 + "oauth_sessions": { 8 + "name": "oauth_sessions", 9 + "columns": { 10 + "key": { 11 + "name": "key", 12 + "type": "text", 13 + "primaryKey": true, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "session": { 18 + "name": "session", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + }, 24 + "created_at": { 25 + "name": "created_at", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true, 29 + "autoincrement": false, 30 + "default": "(datetime('now'))" 31 + }, 32 + "updated_at": { 33 + "name": "updated_at", 34 + "type": "text", 35 + "primaryKey": false, 36 + "notNull": true, 37 + "autoincrement": false, 38 + "default": "(datetime('now'))" 39 + } 40 + }, 41 + "indexes": {}, 42 + "foreignKeys": {}, 43 + "compositePrimaryKeys": {}, 44 + "uniqueConstraints": {}, 45 + "checkConstraints": {} 46 + }, 47 + "oauth_states": { 48 + "name": "oauth_states", 49 + "columns": { 50 + "key": { 51 + "name": "key", 52 + "type": "text", 53 + "primaryKey": true, 54 + "notNull": true, 55 + "autoincrement": false 56 + }, 57 + "state": { 58 + "name": "state", 59 + "type": "text", 60 + "primaryKey": false, 61 + "notNull": true, 62 + "autoincrement": false 63 + }, 64 + "created_at": { 65 + "name": "created_at", 66 + "type": "text", 67 + "primaryKey": false, 68 + "notNull": true, 69 + "autoincrement": false, 70 + "default": "(datetime('now'))" 71 + } 72 + }, 73 + "indexes": {}, 74 + "foreignKeys": {}, 75 + "compositePrimaryKeys": {}, 76 + "uniqueConstraints": {}, 77 + "checkConstraints": {} 78 + }, 79 + "indexer_cursor": { 80 + "name": "indexer_cursor", 81 + "columns": { 82 + "id": { 83 + "name": "id", 84 + "type": "text", 85 + "primaryKey": true, 86 + "notNull": true, 87 + "autoincrement": false, 88 + "default": "'jetstream'" 89 + }, 90 + "cursor": { 91 + "name": "cursor", 92 + "type": "integer", 93 + "primaryKey": false, 94 + "notNull": true, 95 + "autoincrement": false 96 + }, 97 + "updated_at": { 98 + "name": "updated_at", 99 + "type": "text", 100 + "primaryKey": false, 101 + "notNull": true, 102 + "autoincrement": false, 103 + "default": "(datetime('now'))" 104 + } 105 + }, 106 + "indexes": {}, 107 + "foreignKeys": {}, 108 + "compositePrimaryKeys": {}, 109 + "uniqueConstraints": {}, 110 + "checkConstraints": {} 111 + }, 112 + "sphere_entry_counter": { 113 + "name": "sphere_entry_counter", 114 + "columns": { 115 + "sphere_id": { 116 + "name": "sphere_id", 117 + "type": "text", 118 + "primaryKey": true, 119 + "notNull": true, 120 + "autoincrement": false 121 + }, 122 + "last_number": { 123 + "name": "last_number", 124 + "type": "integer", 125 + "primaryKey": false, 126 + "notNull": true, 127 + "autoincrement": false, 128 + "default": 0 129 + } 130 + }, 131 + "indexes": {}, 132 + "foreignKeys": { 133 + "sphere_entry_counter_sphere_id_spheres_id_fk": { 134 + "name": "sphere_entry_counter_sphere_id_spheres_id_fk", 135 + "tableFrom": "sphere_entry_counter", 136 + "tableTo": "spheres", 137 + "columnsFrom": [ 138 + "sphere_id" 139 + ], 140 + "columnsTo": [ 141 + "id" 142 + ], 143 + "onDelete": "no action", 144 + "onUpdate": "no action" 145 + } 146 + }, 147 + "compositePrimaryKeys": {}, 148 + "uniqueConstraints": {}, 149 + "checkConstraints": {} 150 + }, 151 + "entity_labels": { 152 + "name": "entity_labels", 153 + "columns": { 154 + "label_id": { 155 + "name": "label_id", 156 + "type": "text", 157 + "primaryKey": false, 158 + "notNull": true, 159 + "autoincrement": false 160 + }, 161 + "entity_id": { 162 + "name": "entity_id", 163 + "type": "text", 164 + "primaryKey": false, 165 + "notNull": true, 166 + "autoincrement": false 167 + }, 168 + "entity_type": { 169 + "name": "entity_type", 170 + "type": "text", 171 + "primaryKey": false, 172 + "notNull": true, 173 + "autoincrement": false 174 + } 175 + }, 176 + "indexes": { 177 + "idx_entity_labels_entity": { 178 + "name": "idx_entity_labels_entity", 179 + "columns": [ 180 + "entity_id", 181 + "entity_type" 182 + ], 183 + "isUnique": false 184 + } 185 + }, 186 + "foreignKeys": { 187 + "entity_labels_label_id_sphere_labels_id_fk": { 188 + "name": "entity_labels_label_id_sphere_labels_id_fk", 189 + "tableFrom": "entity_labels", 190 + "tableTo": "sphere_labels", 191 + "columnsFrom": [ 192 + "label_id" 193 + ], 194 + "columnsTo": [ 195 + "id" 196 + ], 197 + "onDelete": "cascade", 198 + "onUpdate": "no action" 199 + } 200 + }, 201 + "compositePrimaryKeys": { 202 + "entity_labels_label_id_entity_id_pk": { 203 + "columns": [ 204 + "label_id", 205 + "entity_id" 206 + ], 207 + "name": "entity_labels_label_id_entity_id_pk" 208 + } 209 + }, 210 + "uniqueConstraints": {}, 211 + "checkConstraints": {} 212 + }, 213 + "label_pds_records": { 214 + "name": "label_pds_records", 215 + "columns": { 216 + "entity_id": { 217 + "name": "entity_id", 218 + "type": "text", 219 + "primaryKey": false, 220 + "notNull": true, 221 + "autoincrement": false 222 + }, 223 + "entity_type": { 224 + "name": "entity_type", 225 + "type": "text", 226 + "primaryKey": false, 227 + "notNull": true, 228 + "autoincrement": false 229 + }, 230 + "actor_did": { 231 + "name": "actor_did", 232 + "type": "text", 233 + "primaryKey": false, 234 + "notNull": true, 235 + "autoincrement": false 236 + }, 237 + "rkey": { 238 + "name": "rkey", 239 + "type": "text", 240 + "primaryKey": false, 241 + "notNull": true, 242 + "autoincrement": false 243 + } 244 + }, 245 + "indexes": {}, 246 + "foreignKeys": {}, 247 + "compositePrimaryKeys": { 248 + "label_pds_records_entity_id_entity_type_actor_did_pk": { 249 + "columns": [ 250 + "entity_id", 251 + "entity_type", 252 + "actor_did" 253 + ], 254 + "name": "label_pds_records_entity_id_entity_type_actor_did_pk" 255 + } 256 + }, 257 + "uniqueConstraints": {}, 258 + "checkConstraints": {} 259 + }, 260 + "sphere_labels": { 261 + "name": "sphere_labels", 262 + "columns": { 263 + "id": { 264 + "name": "id", 265 + "type": "text", 266 + "primaryKey": true, 267 + "notNull": true, 268 + "autoincrement": false 269 + }, 270 + "sphere_id": { 271 + "name": "sphere_id", 272 + "type": "text", 273 + "primaryKey": false, 274 + "notNull": true, 275 + "autoincrement": false 276 + }, 277 + "name": { 278 + "name": "name", 279 + "type": "text", 280 + "primaryKey": false, 281 + "notNull": true, 282 + "autoincrement": false 283 + }, 284 + "description": { 285 + "name": "description", 286 + "type": "text", 287 + "primaryKey": false, 288 + "notNull": false, 289 + "autoincrement": false 290 + }, 291 + "color": { 292 + "name": "color", 293 + "type": "text", 294 + "primaryKey": false, 295 + "notNull": true, 296 + "autoincrement": false 297 + }, 298 + "position": { 299 + "name": "position", 300 + "type": "integer", 301 + "primaryKey": false, 302 + "notNull": true, 303 + "autoincrement": false, 304 + "default": 0 305 + }, 306 + "created_at": { 307 + "name": "created_at", 308 + "type": "text", 309 + "primaryKey": false, 310 + "notNull": true, 311 + "autoincrement": false, 312 + "default": "(datetime('now'))" 313 + } 314 + }, 315 + "indexes": { 316 + "idx_sphere_labels_sphere_name": { 317 + "name": "idx_sphere_labels_sphere_name", 318 + "columns": [ 319 + "sphere_id", 320 + "name" 321 + ], 322 + "isUnique": true 323 + }, 324 + "idx_sphere_labels_sphere_position": { 325 + "name": "idx_sphere_labels_sphere_position", 326 + "columns": [ 327 + "sphere_id", 328 + "position" 329 + ], 330 + "isUnique": false 331 + } 332 + }, 333 + "foreignKeys": { 334 + "sphere_labels_sphere_id_spheres_id_fk": { 335 + "name": "sphere_labels_sphere_id_spheres_id_fk", 336 + "tableFrom": "sphere_labels", 337 + "tableTo": "spheres", 338 + "columnsFrom": [ 339 + "sphere_id" 340 + ], 341 + "columnsTo": [ 342 + "id" 343 + ], 344 + "onDelete": "no action", 345 + "onUpdate": "no action" 346 + } 347 + }, 348 + "compositePrimaryKeys": {}, 349 + "uniqueConstraints": {}, 350 + "checkConstraints": {} 351 + }, 352 + "sphere_members": { 353 + "name": "sphere_members", 354 + "columns": { 355 + "sphere_id": { 356 + "name": "sphere_id", 357 + "type": "text", 358 + "primaryKey": false, 359 + "notNull": true, 360 + "autoincrement": false 361 + }, 362 + "did": { 363 + "name": "did", 364 + "type": "text", 365 + "primaryKey": false, 366 + "notNull": true, 367 + "autoincrement": false 368 + }, 369 + "role": { 370 + "name": "role", 371 + "type": "text", 372 + "primaryKey": false, 373 + "notNull": true, 374 + "autoincrement": false, 375 + "default": "'member'" 376 + }, 377 + "status": { 378 + "name": "status", 379 + "type": "text", 380 + "primaryKey": false, 381 + "notNull": true, 382 + "autoincrement": false, 383 + "default": "'invited'" 384 + }, 385 + "invited_by": { 386 + "name": "invited_by", 387 + "type": "text", 388 + "primaryKey": false, 389 + "notNull": false, 390 + "autoincrement": false 391 + }, 392 + "pds_uri": { 393 + "name": "pds_uri", 394 + "type": "text", 395 + "primaryKey": false, 396 + "notNull": false, 397 + "autoincrement": false 398 + }, 399 + "approval_pds_uri": { 400 + "name": "approval_pds_uri", 401 + "type": "text", 402 + "primaryKey": false, 403 + "notNull": false, 404 + "autoincrement": false 405 + }, 406 + "created_at": { 407 + "name": "created_at", 408 + "type": "text", 409 + "primaryKey": false, 410 + "notNull": true, 411 + "autoincrement": false, 412 + "default": "(datetime('now'))" 413 + } 414 + }, 415 + "indexes": { 416 + "idx_sphere_members_did": { 417 + "name": "idx_sphere_members_did", 418 + "columns": [ 419 + "did" 420 + ], 421 + "isUnique": false 422 + } 423 + }, 424 + "foreignKeys": { 425 + "sphere_members_sphere_id_spheres_id_fk": { 426 + "name": "sphere_members_sphere_id_spheres_id_fk", 427 + "tableFrom": "sphere_members", 428 + "tableTo": "spheres", 429 + "columnsFrom": [ 430 + "sphere_id" 431 + ], 432 + "columnsTo": [ 433 + "id" 434 + ], 435 + "onDelete": "no action", 436 + "onUpdate": "no action" 437 + } 438 + }, 439 + "compositePrimaryKeys": { 440 + "sphere_members_sphere_id_did_pk": { 441 + "columns": [ 442 + "sphere_id", 443 + "did" 444 + ], 445 + "name": "sphere_members_sphere_id_did_pk" 446 + } 447 + }, 448 + "uniqueConstraints": {}, 449 + "checkConstraints": {} 450 + }, 451 + "sphere_modules": { 452 + "name": "sphere_modules", 453 + "columns": { 454 + "sphere_id": { 455 + "name": "sphere_id", 456 + "type": "text", 457 + "primaryKey": false, 458 + "notNull": true, 459 + "autoincrement": false 460 + }, 461 + "module_name": { 462 + "name": "module_name", 463 + "type": "text", 464 + "primaryKey": false, 465 + "notNull": true, 466 + "autoincrement": false 467 + }, 468 + "enabled_at": { 469 + "name": "enabled_at", 470 + "type": "text", 471 + "primaryKey": false, 472 + "notNull": true, 473 + "autoincrement": false, 474 + "default": "(datetime('now'))" 475 + } 476 + }, 477 + "indexes": {}, 478 + "foreignKeys": { 479 + "sphere_modules_sphere_id_spheres_id_fk": { 480 + "name": "sphere_modules_sphere_id_spheres_id_fk", 481 + "tableFrom": "sphere_modules", 482 + "tableTo": "spheres", 483 + "columnsFrom": [ 484 + "sphere_id" 485 + ], 486 + "columnsTo": [ 487 + "id" 488 + ], 489 + "onDelete": "no action", 490 + "onUpdate": "no action" 491 + } 492 + }, 493 + "compositePrimaryKeys": { 494 + "sphere_modules_sphere_id_module_name_pk": { 495 + "columns": [ 496 + "sphere_id", 497 + "module_name" 498 + ], 499 + "name": "sphere_modules_sphere_id_module_name_pk" 500 + } 501 + }, 502 + "uniqueConstraints": {}, 503 + "checkConstraints": {} 504 + }, 505 + "sphere_permissions": { 506 + "name": "sphere_permissions", 507 + "columns": { 508 + "sphere_id": { 509 + "name": "sphere_id", 510 + "type": "text", 511 + "primaryKey": false, 512 + "notNull": true, 513 + "autoincrement": false 514 + }, 515 + "action_key": { 516 + "name": "action_key", 517 + "type": "text", 518 + "primaryKey": false, 519 + "notNull": true, 520 + "autoincrement": false 521 + }, 522 + "min_role": { 523 + "name": "min_role", 524 + "type": "text", 525 + "primaryKey": false, 526 + "notNull": true, 527 + "autoincrement": false 528 + }, 529 + "updated_at": { 530 + "name": "updated_at", 531 + "type": "text", 532 + "primaryKey": false, 533 + "notNull": true, 534 + "autoincrement": false, 535 + "default": "(datetime('now'))" 536 + } 537 + }, 538 + "indexes": {}, 539 + "foreignKeys": { 540 + "sphere_permissions_sphere_id_spheres_id_fk": { 541 + "name": "sphere_permissions_sphere_id_spheres_id_fk", 542 + "tableFrom": "sphere_permissions", 543 + "tableTo": "spheres", 544 + "columnsFrom": [ 545 + "sphere_id" 546 + ], 547 + "columnsTo": [ 548 + "id" 549 + ], 550 + "onDelete": "no action", 551 + "onUpdate": "no action" 552 + } 553 + }, 554 + "compositePrimaryKeys": { 555 + "sphere_permissions_sphere_id_action_key_pk": { 556 + "columns": [ 557 + "sphere_id", 558 + "action_key" 559 + ], 560 + "name": "sphere_permissions_sphere_id_action_key_pk" 561 + } 562 + }, 563 + "uniqueConstraints": {}, 564 + "checkConstraints": {} 565 + }, 566 + "spheres": { 567 + "name": "spheres", 568 + "columns": { 569 + "id": { 570 + "name": "id", 571 + "type": "text", 572 + "primaryKey": true, 573 + "notNull": true, 574 + "autoincrement": false 575 + }, 576 + "handle": { 577 + "name": "handle", 578 + "type": "text", 579 + "primaryKey": false, 580 + "notNull": true, 581 + "autoincrement": false 582 + }, 583 + "name": { 584 + "name": "name", 585 + "type": "text", 586 + "primaryKey": false, 587 + "notNull": true, 588 + "autoincrement": false 589 + }, 590 + "description": { 591 + "name": "description", 592 + "type": "text", 593 + "primaryKey": false, 594 + "notNull": false, 595 + "autoincrement": false 596 + }, 597 + "visibility": { 598 + "name": "visibility", 599 + "type": "text", 600 + "primaryKey": false, 601 + "notNull": true, 602 + "autoincrement": false, 603 + "default": "'public'" 604 + }, 605 + "owner_did": { 606 + "name": "owner_did", 607 + "type": "text", 608 + "primaryKey": false, 609 + "notNull": true, 610 + "autoincrement": false 611 + }, 612 + "pds_uri": { 613 + "name": "pds_uri", 614 + "type": "text", 615 + "primaryKey": false, 616 + "notNull": false, 617 + "autoincrement": false 618 + }, 619 + "created_at": { 620 + "name": "created_at", 621 + "type": "text", 622 + "primaryKey": false, 623 + "notNull": true, 624 + "autoincrement": false, 625 + "default": "(datetime('now'))" 626 + }, 627 + "updated_at": { 628 + "name": "updated_at", 629 + "type": "text", 630 + "primaryKey": false, 631 + "notNull": true, 632 + "autoincrement": false, 633 + "default": "(datetime('now'))" 634 + } 635 + }, 636 + "indexes": { 637 + "spheres_handle_unique": { 638 + "name": "spheres_handle_unique", 639 + "columns": [ 640 + "handle" 641 + ], 642 + "isUnique": true 643 + } 644 + }, 645 + "foreignKeys": {}, 646 + "compositePrimaryKeys": {}, 647 + "uniqueConstraints": {}, 648 + "checkConstraints": {} 649 + }, 650 + "feature_request_comment_votes": { 651 + "name": "feature_request_comment_votes", 652 + "columns": { 653 + "comment_id": { 654 + "name": "comment_id", 655 + "type": "text", 656 + "primaryKey": false, 657 + "notNull": true, 658 + "autoincrement": false 659 + }, 660 + "author_did": { 661 + "name": "author_did", 662 + "type": "text", 663 + "primaryKey": false, 664 + "notNull": true, 665 + "autoincrement": false 666 + }, 667 + "pds_uri": { 668 + "name": "pds_uri", 669 + "type": "text", 670 + "primaryKey": false, 671 + "notNull": false, 672 + "autoincrement": false 673 + }, 674 + "created_at": { 675 + "name": "created_at", 676 + "type": "text", 677 + "primaryKey": false, 678 + "notNull": true, 679 + "autoincrement": false, 680 + "default": "(datetime('now'))" 681 + } 682 + }, 683 + "indexes": { 684 + "idx_feature_request_comment_votes_comment": { 685 + "name": "idx_feature_request_comment_votes_comment", 686 + "columns": [ 687 + "comment_id" 688 + ], 689 + "isUnique": false 690 + } 691 + }, 692 + "foreignKeys": { 693 + "feature_request_comment_votes_comment_id_feature_request_comments_id_fk": { 694 + "name": "feature_request_comment_votes_comment_id_feature_request_comments_id_fk", 695 + "tableFrom": "feature_request_comment_votes", 696 + "tableTo": "feature_request_comments", 697 + "columnsFrom": [ 698 + "comment_id" 699 + ], 700 + "columnsTo": [ 701 + "id" 702 + ], 703 + "onDelete": "no action", 704 + "onUpdate": "no action" 705 + } 706 + }, 707 + "compositePrimaryKeys": { 708 + "feature_request_comment_votes_comment_id_author_did_pk": { 709 + "columns": [ 710 + "comment_id", 711 + "author_did" 712 + ], 713 + "name": "feature_request_comment_votes_comment_id_author_did_pk" 714 + } 715 + }, 716 + "uniqueConstraints": {}, 717 + "checkConstraints": {} 718 + }, 719 + "feature_request_comments": { 720 + "name": "feature_request_comments", 721 + "columns": { 722 + "id": { 723 + "name": "id", 724 + "type": "text", 725 + "primaryKey": true, 726 + "notNull": true, 727 + "autoincrement": false 728 + }, 729 + "request_id": { 730 + "name": "request_id", 731 + "type": "text", 732 + "primaryKey": false, 733 + "notNull": true, 734 + "autoincrement": false 735 + }, 736 + "author_did": { 737 + "name": "author_did", 738 + "type": "text", 739 + "primaryKey": false, 740 + "notNull": true, 741 + "autoincrement": false 742 + }, 743 + "content": { 744 + "name": "content", 745 + "type": "text", 746 + "primaryKey": false, 747 + "notNull": true, 748 + "autoincrement": false 749 + }, 750 + "pds_uri": { 751 + "name": "pds_uri", 752 + "type": "text", 753 + "primaryKey": false, 754 + "notNull": false, 755 + "autoincrement": false 756 + }, 757 + "updated_at": { 758 + "name": "updated_at", 759 + "type": "text", 760 + "primaryKey": false, 761 + "notNull": true, 762 + "autoincrement": false, 763 + "default": "(datetime('now'))" 764 + }, 765 + "hidden_at": { 766 + "name": "hidden_at", 767 + "type": "text", 768 + "primaryKey": false, 769 + "notNull": false, 770 + "autoincrement": false 771 + }, 772 + "moderated_by": { 773 + "name": "moderated_by", 774 + "type": "text", 775 + "primaryKey": false, 776 + "notNull": false, 777 + "autoincrement": false 778 + } 779 + }, 780 + "indexes": { 781 + "idx_feature_request_comments_request": { 782 + "name": "idx_feature_request_comments_request", 783 + "columns": [ 784 + "request_id" 785 + ], 786 + "isUnique": false 787 + }, 788 + "idx_feature_request_comments_author_request": { 789 + "name": "idx_feature_request_comments_author_request", 790 + "columns": [ 791 + "author_did", 792 + "request_id" 793 + ], 794 + "isUnique": false 795 + } 796 + }, 797 + "foreignKeys": { 798 + "feature_request_comments_request_id_feature_requests_id_fk": { 799 + "name": "feature_request_comments_request_id_feature_requests_id_fk", 800 + "tableFrom": "feature_request_comments", 801 + "tableTo": "feature_requests", 802 + "columnsFrom": [ 803 + "request_id" 804 + ], 805 + "columnsTo": [ 806 + "id" 807 + ], 808 + "onDelete": "no action", 809 + "onUpdate": "no action" 810 + } 811 + }, 812 + "compositePrimaryKeys": {}, 813 + "uniqueConstraints": {}, 814 + "checkConstraints": {} 815 + }, 816 + "feature_request_statuses": { 817 + "name": "feature_request_statuses", 818 + "columns": { 819 + "id": { 820 + "name": "id", 821 + "type": "text", 822 + "primaryKey": true, 823 + "notNull": true, 824 + "autoincrement": false 825 + }, 826 + "request_id": { 827 + "name": "request_id", 828 + "type": "text", 829 + "primaryKey": false, 830 + "notNull": true, 831 + "autoincrement": false 832 + }, 833 + "author_did": { 834 + "name": "author_did", 835 + "type": "text", 836 + "primaryKey": false, 837 + "notNull": true, 838 + "autoincrement": false 839 + }, 840 + "status": { 841 + "name": "status", 842 + "type": "text", 843 + "primaryKey": false, 844 + "notNull": true, 845 + "autoincrement": false 846 + }, 847 + "pds_uri": { 848 + "name": "pds_uri", 849 + "type": "text", 850 + "primaryKey": false, 851 + "notNull": false, 852 + "autoincrement": false 853 + } 854 + }, 855 + "indexes": { 856 + "idx_feature_request_statuses_request": { 857 + "name": "idx_feature_request_statuses_request", 858 + "columns": [ 859 + "request_id" 860 + ], 861 + "isUnique": false 862 + } 863 + }, 864 + "foreignKeys": { 865 + "feature_request_statuses_request_id_feature_requests_id_fk": { 866 + "name": "feature_request_statuses_request_id_feature_requests_id_fk", 867 + "tableFrom": "feature_request_statuses", 868 + "tableTo": "feature_requests", 869 + "columnsFrom": [ 870 + "request_id" 871 + ], 872 + "columnsTo": [ 873 + "id" 874 + ], 875 + "onDelete": "no action", 876 + "onUpdate": "no action" 877 + } 878 + }, 879 + "compositePrimaryKeys": {}, 880 + "uniqueConstraints": {}, 881 + "checkConstraints": {} 882 + }, 883 + "feature_request_votes": { 884 + "name": "feature_request_votes", 885 + "columns": { 886 + "request_id": { 887 + "name": "request_id", 888 + "type": "text", 889 + "primaryKey": false, 890 + "notNull": true, 891 + "autoincrement": false 892 + }, 893 + "author_did": { 894 + "name": "author_did", 895 + "type": "text", 896 + "primaryKey": false, 897 + "notNull": true, 898 + "autoincrement": false 899 + }, 900 + "pds_uri": { 901 + "name": "pds_uri", 902 + "type": "text", 903 + "primaryKey": false, 904 + "notNull": false, 905 + "autoincrement": false 906 + }, 907 + "created_at": { 908 + "name": "created_at", 909 + "type": "text", 910 + "primaryKey": false, 911 + "notNull": true, 912 + "autoincrement": false, 913 + "default": "(datetime('now'))" 914 + } 915 + }, 916 + "indexes": { 917 + "idx_feature_request_votes_request": { 918 + "name": "idx_feature_request_votes_request", 919 + "columns": [ 920 + "request_id" 921 + ], 922 + "isUnique": false 923 + } 924 + }, 925 + "foreignKeys": { 926 + "feature_request_votes_request_id_feature_requests_id_fk": { 927 + "name": "feature_request_votes_request_id_feature_requests_id_fk", 928 + "tableFrom": "feature_request_votes", 929 + "tableTo": "feature_requests", 930 + "columnsFrom": [ 931 + "request_id" 932 + ], 933 + "columnsTo": [ 934 + "id" 935 + ], 936 + "onDelete": "no action", 937 + "onUpdate": "no action" 938 + } 939 + }, 940 + "compositePrimaryKeys": { 941 + "feature_request_votes_request_id_author_did_pk": { 942 + "columns": [ 943 + "request_id", 944 + "author_did" 945 + ], 946 + "name": "feature_request_votes_request_id_author_did_pk" 947 + } 948 + }, 949 + "uniqueConstraints": {}, 950 + "checkConstraints": {} 951 + }, 952 + "feature_requests": { 953 + "name": "feature_requests", 954 + "columns": { 955 + "id": { 956 + "name": "id", 957 + "type": "text", 958 + "primaryKey": true, 959 + "notNull": true, 960 + "autoincrement": false 961 + }, 962 + "sphere_id": { 963 + "name": "sphere_id", 964 + "type": "text", 965 + "primaryKey": false, 966 + "notNull": true, 967 + "autoincrement": false 968 + }, 969 + "number": { 970 + "name": "number", 971 + "type": "integer", 972 + "primaryKey": false, 973 + "notNull": true, 974 + "autoincrement": false 975 + }, 976 + "author_did": { 977 + "name": "author_did", 978 + "type": "text", 979 + "primaryKey": false, 980 + "notNull": true, 981 + "autoincrement": false 982 + }, 983 + "title": { 984 + "name": "title", 985 + "type": "text", 986 + "primaryKey": false, 987 + "notNull": true, 988 + "autoincrement": false 989 + }, 990 + "description": { 991 + "name": "description", 992 + "type": "text", 993 + "primaryKey": false, 994 + "notNull": true, 995 + "autoincrement": false 996 + }, 997 + "status": { 998 + "name": "status", 999 + "type": "text", 1000 + "primaryKey": false, 1001 + "notNull": true, 1002 + "autoincrement": false, 1003 + "default": "'requested'" 1004 + }, 1005 + "duplicate_of_id": { 1006 + "name": "duplicate_of_id", 1007 + "type": "text", 1008 + "primaryKey": false, 1009 + "notNull": false, 1010 + "autoincrement": false 1011 + }, 1012 + "pds_uri": { 1013 + "name": "pds_uri", 1014 + "type": "text", 1015 + "primaryKey": false, 1016 + "notNull": false, 1017 + "autoincrement": false 1018 + }, 1019 + "hidden_at": { 1020 + "name": "hidden_at", 1021 + "type": "text", 1022 + "primaryKey": false, 1023 + "notNull": false, 1024 + "autoincrement": false 1025 + }, 1026 + "moderated_by": { 1027 + "name": "moderated_by", 1028 + "type": "text", 1029 + "primaryKey": false, 1030 + "notNull": false, 1031 + "autoincrement": false 1032 + }, 1033 + "label_updated_at": { 1034 + "name": "label_updated_at", 1035 + "type": "text", 1036 + "primaryKey": false, 1037 + "notNull": false, 1038 + "autoincrement": false 1039 + }, 1040 + "updated_at": { 1041 + "name": "updated_at", 1042 + "type": "text", 1043 + "primaryKey": false, 1044 + "notNull": true, 1045 + "autoincrement": false, 1046 + "default": "(datetime('now'))" 1047 + } 1048 + }, 1049 + "indexes": { 1050 + "idx_feature_requests_sphere_number": { 1051 + "name": "idx_feature_requests_sphere_number", 1052 + "columns": [ 1053 + "sphere_id", 1054 + "number" 1055 + ], 1056 + "isUnique": true 1057 + }, 1058 + "idx_feature_requests_sphere": { 1059 + "name": "idx_feature_requests_sphere", 1060 + "columns": [ 1061 + "sphere_id" 1062 + ], 1063 + "isUnique": false 1064 + }, 1065 + "idx_feature_requests_status": { 1066 + "name": "idx_feature_requests_status", 1067 + "columns": [ 1068 + "status" 1069 + ], 1070 + "isUnique": false 1071 + } 1072 + }, 1073 + "foreignKeys": { 1074 + "feature_requests_sphere_id_spheres_id_fk": { 1075 + "name": "feature_requests_sphere_id_spheres_id_fk", 1076 + "tableFrom": "feature_requests", 1077 + "tableTo": "spheres", 1078 + "columnsFrom": [ 1079 + "sphere_id" 1080 + ], 1081 + "columnsTo": [ 1082 + "id" 1083 + ], 1084 + "onDelete": "no action", 1085 + "onUpdate": "no action" 1086 + } 1087 + }, 1088 + "compositePrimaryKeys": {}, 1089 + "uniqueConstraints": {}, 1090 + "checkConstraints": {} 1091 + }, 1092 + "feed_posts": { 1093 + "name": "feed_posts", 1094 + "columns": { 1095 + "id": { 1096 + "name": "id", 1097 + "type": "text", 1098 + "primaryKey": true, 1099 + "notNull": true, 1100 + "autoincrement": false 1101 + }, 1102 + "author_did": { 1103 + "name": "author_did", 1104 + "type": "text", 1105 + "primaryKey": false, 1106 + "notNull": true, 1107 + "autoincrement": false 1108 + }, 1109 + "content": { 1110 + "name": "content", 1111 + "type": "text", 1112 + "primaryKey": false, 1113 + "notNull": true, 1114 + "autoincrement": false 1115 + }, 1116 + "parent_id": { 1117 + "name": "parent_id", 1118 + "type": "text", 1119 + "primaryKey": false, 1120 + "notNull": false, 1121 + "autoincrement": false 1122 + }, 1123 + "pds_uri": { 1124 + "name": "pds_uri", 1125 + "type": "text", 1126 + "primaryKey": false, 1127 + "notNull": false, 1128 + "autoincrement": false 1129 + }, 1130 + "updated_at": { 1131 + "name": "updated_at", 1132 + "type": "text", 1133 + "primaryKey": false, 1134 + "notNull": true, 1135 + "autoincrement": false, 1136 + "default": "(datetime('now'))" 1137 + } 1138 + }, 1139 + "indexes": { 1140 + "idx_feed_posts_parent": { 1141 + "name": "idx_feed_posts_parent", 1142 + "columns": [ 1143 + "parent_id" 1144 + ], 1145 + "isUnique": false 1146 + } 1147 + }, 1148 + "foreignKeys": {}, 1149 + "compositePrimaryKeys": {}, 1150 + "uniqueConstraints": {}, 1151 + "checkConstraints": {} 1152 + }, 1153 + "kanban_columns": { 1154 + "name": "kanban_columns", 1155 + "columns": { 1156 + "id": { 1157 + "name": "id", 1158 + "type": "text", 1159 + "primaryKey": true, 1160 + "notNull": true, 1161 + "autoincrement": false 1162 + }, 1163 + "sphere_id": { 1164 + "name": "sphere_id", 1165 + "type": "text", 1166 + "primaryKey": false, 1167 + "notNull": true, 1168 + "autoincrement": false 1169 + }, 1170 + "slug": { 1171 + "name": "slug", 1172 + "type": "text", 1173 + "primaryKey": false, 1174 + "notNull": true, 1175 + "autoincrement": false 1176 + }, 1177 + "label": { 1178 + "name": "label", 1179 + "type": "text", 1180 + "primaryKey": false, 1181 + "notNull": true, 1182 + "autoincrement": false 1183 + }, 1184 + "status_type": { 1185 + "name": "status_type", 1186 + "type": "text", 1187 + "primaryKey": false, 1188 + "notNull": true, 1189 + "autoincrement": false, 1190 + "default": "'backlog'" 1191 + }, 1192 + "position": { 1193 + "name": "position", 1194 + "type": "integer", 1195 + "primaryKey": false, 1196 + "notNull": true, 1197 + "autoincrement": false 1198 + }, 1199 + "created_at": { 1200 + "name": "created_at", 1201 + "type": "text", 1202 + "primaryKey": false, 1203 + "notNull": true, 1204 + "autoincrement": false, 1205 + "default": "(datetime('now'))" 1206 + } 1207 + }, 1208 + "indexes": { 1209 + "idx_kanban_columns_sphere_slug": { 1210 + "name": "idx_kanban_columns_sphere_slug", 1211 + "columns": [ 1212 + "sphere_id", 1213 + "slug" 1214 + ], 1215 + "isUnique": true 1216 + }, 1217 + "idx_kanban_columns_sphere_position": { 1218 + "name": "idx_kanban_columns_sphere_position", 1219 + "columns": [ 1220 + "sphere_id", 1221 + "position" 1222 + ], 1223 + "isUnique": false 1224 + } 1225 + }, 1226 + "foreignKeys": { 1227 + "kanban_columns_sphere_id_spheres_id_fk": { 1228 + "name": "kanban_columns_sphere_id_spheres_id_fk", 1229 + "tableFrom": "kanban_columns", 1230 + "tableTo": "spheres", 1231 + "columnsFrom": [ 1232 + "sphere_id" 1233 + ], 1234 + "columnsTo": [ 1235 + "id" 1236 + ], 1237 + "onDelete": "no action", 1238 + "onUpdate": "no action" 1239 + } 1240 + }, 1241 + "compositePrimaryKeys": {}, 1242 + "uniqueConstraints": {}, 1243 + "checkConstraints": {} 1244 + }, 1245 + "kanban_task_comments": { 1246 + "name": "kanban_task_comments", 1247 + "columns": { 1248 + "id": { 1249 + "name": "id", 1250 + "type": "text", 1251 + "primaryKey": true, 1252 + "notNull": true, 1253 + "autoincrement": false 1254 + }, 1255 + "task_id": { 1256 + "name": "task_id", 1257 + "type": "text", 1258 + "primaryKey": false, 1259 + "notNull": true, 1260 + "autoincrement": false 1261 + }, 1262 + "author_did": { 1263 + "name": "author_did", 1264 + "type": "text", 1265 + "primaryKey": false, 1266 + "notNull": true, 1267 + "autoincrement": false 1268 + }, 1269 + "content": { 1270 + "name": "content", 1271 + "type": "text", 1272 + "primaryKey": false, 1273 + "notNull": true, 1274 + "autoincrement": false 1275 + }, 1276 + "pds_uri": { 1277 + "name": "pds_uri", 1278 + "type": "text", 1279 + "primaryKey": false, 1280 + "notNull": false, 1281 + "autoincrement": false 1282 + }, 1283 + "updated_at": { 1284 + "name": "updated_at", 1285 + "type": "text", 1286 + "primaryKey": false, 1287 + "notNull": true, 1288 + "autoincrement": false, 1289 + "default": "(datetime('now'))" 1290 + }, 1291 + "hidden_at": { 1292 + "name": "hidden_at", 1293 + "type": "text", 1294 + "primaryKey": false, 1295 + "notNull": false, 1296 + "autoincrement": false 1297 + }, 1298 + "moderated_by": { 1299 + "name": "moderated_by", 1300 + "type": "text", 1301 + "primaryKey": false, 1302 + "notNull": false, 1303 + "autoincrement": false 1304 + } 1305 + }, 1306 + "indexes": { 1307 + "idx_kanban_task_comments_task": { 1308 + "name": "idx_kanban_task_comments_task", 1309 + "columns": [ 1310 + "task_id" 1311 + ], 1312 + "isUnique": false 1313 + }, 1314 + "idx_kanban_task_comments_author_task": { 1315 + "name": "idx_kanban_task_comments_author_task", 1316 + "columns": [ 1317 + "author_did", 1318 + "task_id" 1319 + ], 1320 + "isUnique": false 1321 + } 1322 + }, 1323 + "foreignKeys": { 1324 + "kanban_task_comments_task_id_kanban_tasks_id_fk": { 1325 + "name": "kanban_task_comments_task_id_kanban_tasks_id_fk", 1326 + "tableFrom": "kanban_task_comments", 1327 + "tableTo": "kanban_tasks", 1328 + "columnsFrom": [ 1329 + "task_id" 1330 + ], 1331 + "columnsTo": [ 1332 + "id" 1333 + ], 1334 + "onDelete": "no action", 1335 + "onUpdate": "no action" 1336 + } 1337 + }, 1338 + "compositePrimaryKeys": {}, 1339 + "uniqueConstraints": {}, 1340 + "checkConstraints": {} 1341 + }, 1342 + "kanban_task_status_changes": { 1343 + "name": "kanban_task_status_changes", 1344 + "columns": { 1345 + "id": { 1346 + "name": "id", 1347 + "type": "text", 1348 + "primaryKey": true, 1349 + "notNull": true, 1350 + "autoincrement": false 1351 + }, 1352 + "task_id": { 1353 + "name": "task_id", 1354 + "type": "text", 1355 + "primaryKey": false, 1356 + "notNull": true, 1357 + "autoincrement": false 1358 + }, 1359 + "author_did": { 1360 + "name": "author_did", 1361 + "type": "text", 1362 + "primaryKey": false, 1363 + "notNull": true, 1364 + "autoincrement": false 1365 + }, 1366 + "status": { 1367 + "name": "status", 1368 + "type": "text", 1369 + "primaryKey": false, 1370 + "notNull": true, 1371 + "autoincrement": false 1372 + }, 1373 + "pds_uri": { 1374 + "name": "pds_uri", 1375 + "type": "text", 1376 + "primaryKey": false, 1377 + "notNull": false, 1378 + "autoincrement": false 1379 + } 1380 + }, 1381 + "indexes": { 1382 + "idx_kanban_task_status_changes_task": { 1383 + "name": "idx_kanban_task_status_changes_task", 1384 + "columns": [ 1385 + "task_id" 1386 + ], 1387 + "isUnique": false 1388 + } 1389 + }, 1390 + "foreignKeys": { 1391 + "kanban_task_status_changes_task_id_kanban_tasks_id_fk": { 1392 + "name": "kanban_task_status_changes_task_id_kanban_tasks_id_fk", 1393 + "tableFrom": "kanban_task_status_changes", 1394 + "tableTo": "kanban_tasks", 1395 + "columnsFrom": [ 1396 + "task_id" 1397 + ], 1398 + "columnsTo": [ 1399 + "id" 1400 + ], 1401 + "onDelete": "no action", 1402 + "onUpdate": "no action" 1403 + } 1404 + }, 1405 + "compositePrimaryKeys": {}, 1406 + "uniqueConstraints": {}, 1407 + "checkConstraints": {} 1408 + }, 1409 + "kanban_tasks": { 1410 + "name": "kanban_tasks", 1411 + "columns": { 1412 + "id": { 1413 + "name": "id", 1414 + "type": "text", 1415 + "primaryKey": true, 1416 + "notNull": true, 1417 + "autoincrement": false 1418 + }, 1419 + "sphere_id": { 1420 + "name": "sphere_id", 1421 + "type": "text", 1422 + "primaryKey": false, 1423 + "notNull": true, 1424 + "autoincrement": false 1425 + }, 1426 + "number": { 1427 + "name": "number", 1428 + "type": "integer", 1429 + "primaryKey": false, 1430 + "notNull": true, 1431 + "autoincrement": false 1432 + }, 1433 + "author_did": { 1434 + "name": "author_did", 1435 + "type": "text", 1436 + "primaryKey": false, 1437 + "notNull": true, 1438 + "autoincrement": false 1439 + }, 1440 + "title": { 1441 + "name": "title", 1442 + "type": "text", 1443 + "primaryKey": false, 1444 + "notNull": true, 1445 + "autoincrement": false 1446 + }, 1447 + "description": { 1448 + "name": "description", 1449 + "type": "text", 1450 + "primaryKey": false, 1451 + "notNull": true, 1452 + "autoincrement": false, 1453 + "default": "''" 1454 + }, 1455 + "status": { 1456 + "name": "status", 1457 + "type": "text", 1458 + "primaryKey": false, 1459 + "notNull": true, 1460 + "autoincrement": false, 1461 + "default": "'backlog'" 1462 + }, 1463 + "position": { 1464 + "name": "position", 1465 + "type": "integer", 1466 + "primaryKey": false, 1467 + "notNull": true, 1468 + "autoincrement": false, 1469 + "default": 0 1470 + }, 1471 + "assignee_did": { 1472 + "name": "assignee_did", 1473 + "type": "text", 1474 + "primaryKey": false, 1475 + "notNull": false, 1476 + "autoincrement": false 1477 + }, 1478 + "pds_uri": { 1479 + "name": "pds_uri", 1480 + "type": "text", 1481 + "primaryKey": false, 1482 + "notNull": false, 1483 + "autoincrement": false 1484 + }, 1485 + "hidden_at": { 1486 + "name": "hidden_at", 1487 + "type": "text", 1488 + "primaryKey": false, 1489 + "notNull": false, 1490 + "autoincrement": false 1491 + }, 1492 + "moderated_by": { 1493 + "name": "moderated_by", 1494 + "type": "text", 1495 + "primaryKey": false, 1496 + "notNull": false, 1497 + "autoincrement": false 1498 + }, 1499 + "label_updated_at": { 1500 + "name": "label_updated_at", 1501 + "type": "text", 1502 + "primaryKey": false, 1503 + "notNull": false, 1504 + "autoincrement": false 1505 + }, 1506 + "updated_at": { 1507 + "name": "updated_at", 1508 + "type": "text", 1509 + "primaryKey": false, 1510 + "notNull": true, 1511 + "autoincrement": false, 1512 + "default": "(datetime('now'))" 1513 + } 1514 + }, 1515 + "indexes": { 1516 + "idx_kanban_tasks_sphere_number": { 1517 + "name": "idx_kanban_tasks_sphere_number", 1518 + "columns": [ 1519 + "sphere_id", 1520 + "number" 1521 + ], 1522 + "isUnique": true 1523 + }, 1524 + "idx_kanban_tasks_sphere": { 1525 + "name": "idx_kanban_tasks_sphere", 1526 + "columns": [ 1527 + "sphere_id" 1528 + ], 1529 + "isUnique": false 1530 + }, 1531 + "idx_kanban_tasks_status": { 1532 + "name": "idx_kanban_tasks_status", 1533 + "columns": [ 1534 + "status" 1535 + ], 1536 + "isUnique": false 1537 + }, 1538 + "idx_kanban_tasks_sphere_status_position": { 1539 + "name": "idx_kanban_tasks_sphere_status_position", 1540 + "columns": [ 1541 + "sphere_id", 1542 + "status", 1543 + "position" 1544 + ], 1545 + "isUnique": false 1546 + } 1547 + }, 1548 + "foreignKeys": { 1549 + "kanban_tasks_sphere_id_spheres_id_fk": { 1550 + "name": "kanban_tasks_sphere_id_spheres_id_fk", 1551 + "tableFrom": "kanban_tasks", 1552 + "tableTo": "spheres", 1553 + "columnsFrom": [ 1554 + "sphere_id" 1555 + ], 1556 + "columnsTo": [ 1557 + "id" 1558 + ], 1559 + "onDelete": "no action", 1560 + "onUpdate": "no action" 1561 + } 1562 + }, 1563 + "compositePrimaryKeys": {}, 1564 + "uniqueConstraints": {}, 1565 + "checkConstraints": {} 1566 + } 1567 + }, 1568 + "views": {}, 1569 + "enums": {}, 1570 + "_meta": { 1571 + "schemas": {}, 1572 + "tables": {}, 1573 + "columns": { 1574 + "\"feature_requests\".\"label_tid\"": "\"feature_requests\".\"label_updated_at\"", 1575 + "\"kanban_tasks\".\"label_tid\"": "\"kanban_tasks\".\"label_updated_at\"" 1576 + } 1577 + }, 1578 + "internal": { 1579 + "indexes": {} 1580 + } 1581 + }
+15 -1
drizzle/meta/_journal.json
··· 57 57 "when": 1776681600000, 58 58 "tag": "0007_label_pds_records", 59 59 "breakpoints": true 60 + }, 61 + { 62 + "idx": 8, 63 + "version": "6", 64 + "when": 1776800000000, 65 + "tag": "0008_kanban_status_type", 66 + "breakpoints": true 67 + }, 68 + { 69 + "idx": 9, 70 + "version": "6", 71 + "when": 1776516974088, 72 + "tag": "0009_damp_microbe", 73 + "breakpoints": true 60 74 } 61 75 ] 62 - } 76 + }
+4
packages/core/src/generated/lexicon-records.ts
··· 62 62 description?: string; 63 63 /** Column slug the task belongs to. Values are user-defined per Sphere. */ 64 64 status: string; 65 + /** Canonical status type the column maps to. Third-party indexers use this to categorize user-defined slugs without needing the sphere's column config. */ 66 + statusType: string; 65 67 /** did — DID of the Sphere owner (the identity hosting the site.exosphere.sphere.profile record). */ 66 68 subject: string; 67 69 /** did — DID of the member assigned to this task. */ ··· 92 94 subject: string; 93 95 /** Column slug the task is moved to. Values are user-defined per Sphere. */ 94 96 status: string; 97 + /** Canonical status type the column maps to. Third-party indexers use this to categorize user-defined slugs without needing the sphere's column config. */ 98 + statusType: string; 95 99 } 96 100 97 101 export interface ModerationRecord {
+48
packages/kanban/src/__tests__/schemas.test.ts
··· 6 6 updateStatusSchema, 7 7 assignTaskSchema, 8 8 reorderSchema, 9 + defaultColumns, 9 10 } from "../schemas/task.ts"; 10 11 import { createCommentSchema, updateCommentSchema } from "../schemas/comment.ts"; 12 + import { createColumnSchema, updateColumnSchema } from "../schemas/column.ts"; 13 + import { STATUS_TYPES } from "../schemas/status-type.ts"; 11 14 12 15 describe("createTaskSchema", () => { 13 16 const valid = { title: "Implement auth flow" }; ··· 142 145 expect(() => updateCommentSchema.parse({ content: "" })).toThrow(); 143 146 }); 144 147 }); 148 + 149 + describe("createColumnSchema", () => { 150 + it("accepts a valid label and statusType", () => { 151 + const result = createColumnSchema.parse({ label: "In review", statusType: "started" }); 152 + expect(result.label).toBe("In review"); 153 + expect(result.statusType).toBe("started"); 154 + }); 155 + 156 + it("rejects an unknown statusType", () => { 157 + expect(() => createColumnSchema.parse({ label: "Weird", statusType: "made-up" })).toThrow(); 158 + }); 159 + 160 + it("rejects a missing statusType", () => { 161 + expect(() => createColumnSchema.parse({ label: "No type" })).toThrow(); 162 + }); 163 + }); 164 + 165 + describe("updateColumnSchema", () => { 166 + it("accepts a label-only update", () => { 167 + const result = updateColumnSchema.parse({ label: "Renamed" }); 168 + expect(result.label).toBe("Renamed"); 169 + expect(result.statusType).toBeUndefined(); 170 + }); 171 + 172 + it("accepts a statusType-only update", () => { 173 + const result = updateColumnSchema.parse({ statusType: "canceled" }); 174 + expect(result.statusType).toBe("canceled"); 175 + }); 176 + 177 + it("rejects an empty body", () => { 178 + expect(() => updateColumnSchema.parse({})).toThrow(); 179 + }); 180 + 181 + it("rejects an unknown statusType", () => { 182 + expect(() => updateColumnSchema.parse({ statusType: "nope" })).toThrow(); 183 + }); 184 + }); 185 + 186 + describe("defaultColumns", () => { 187 + it("every entry maps to a valid StatusType", () => { 188 + for (const col of defaultColumns) { 189 + expect(STATUS_TYPES).toContain(col.statusType); 190 + } 191 + }); 192 + });
+25 -12
packages/kanban/src/api/columns.ts
··· 12 12 import { 13 13 getColumns, 14 14 insertColumn, 15 - updateColumnLabel, 15 + updateColumn, 16 16 deleteColumnAndReassign, 17 17 reorderColumns, 18 18 } from "../db/operations.ts"; 19 19 import type { KanbanColumnDef } from "../types.ts"; 20 + import type { StatusType } from "../schemas/status-type.ts"; 21 + import type { KanbanColumn } from "../db/schema.ts"; 20 22 21 23 const MODULE = "kanban"; 22 24 23 - function toColumnDef(col: { 24 - id: string; 25 - slug: string; 26 - label: string; 27 - position: number; 28 - }): KanbanColumnDef { 29 - return { id: col.id, slug: col.slug, label: col.label, position: col.position }; 25 + export function toColumnDef(col: KanbanColumn): KanbanColumnDef { 26 + return { 27 + id: col.id, 28 + slug: col.slug, 29 + label: col.label, 30 + statusType: col.statusType as StatusType, 31 + position: col.position, 32 + }; 30 33 } 31 34 32 35 const app = new Hono<AuthEnv & SphereEnv>(); ··· 43 46 const result = createColumnSchema.safeParse(body); 44 47 if (!result.success) return c.json({ error: z.flattenError(result.error) }, 400); 45 48 46 - const col = insertColumn({ sphereId: c.var.sphereId, label: result.data.label }); 49 + const col = insertColumn({ 50 + sphereId: c.var.sphereId, 51 + label: result.data.label, 52 + statusType: result.data.statusType, 53 + }); 47 54 return c.json({ column: toColumnDef(col) }, 201); 48 55 }); 49 56 50 - // Rename column 57 + // Update column (rename and/or retype) 51 58 app.put("/columns/:id", requireAuth, requirePermission(MODULE, "manageSettings"), async (c) => { 52 59 const id = c.req.param("id"); 53 60 const body = await c.req.json(); ··· 58 65 const existing = cols.find((col) => col.id === id); 59 66 if (!existing) return c.json({ error: "Column not found" }, 404); 60 67 61 - updateColumnLabel(id, result.data.label); 62 - return c.json({ column: toColumnDef({ ...existing, label: result.data.label }) }); 68 + updateColumn(id, result.data); 69 + return c.json({ 70 + column: toColumnDef({ 71 + ...existing, 72 + label: result.data.label ?? existing.label, 73 + statusType: result.data.statusType ?? existing.statusType, 74 + }), 75 + }); 63 76 }); 64 77 65 78 // Delete column (reassign tasks to another)
+10 -5
packages/kanban/src/api/tasks.ts
··· 34 34 setEntityLabelsSchema, 35 35 writeLabelPdsRecord, 36 36 } from "@exosphere/core/sphere"; 37 + import { toColumnDef } from "./columns.ts"; 37 38 38 39 const COLLECTION = "site.exosphere.kanban.entry"; 39 40 const STATUS_COLLECTION = "site.exosphere.kanban.status"; ··· 159 160 } 160 161 161 162 return c.json({ 162 - columns: cols.map(({ id, slug, label, position }) => ({ id, slug, label, position })), 163 + columns: cols.map(toColumnDef), 163 164 tasksByColumn, 164 165 tasks, 165 166 }); ··· 183 184 184 185 // Validate status against this sphere's columns; fall back to first column 185 186 const cols = getColumns(sphereId); 186 - if (!cols.some((col) => col.slug === status)) { 187 - status = cols[0].slug; 188 - } 187 + const column = cols.find((col) => col.slug === status) ?? cols[0]; 188 + status = column.slug; 189 189 190 190 let pdsUri: string | null = null; 191 191 ··· 195 195 title, 196 196 description, 197 197 status, 198 + statusType: column.statusType, 198 199 subject: sphereOwnerDid, 199 200 assigneeDid: assigneeDid ?? undefined, 200 201 }); ··· 276 277 277 278 if (existing.pdsUri) { 278 279 const session = c.var.session; 280 + const column = getColumnBySlug(sphereId, existing.status); 279 281 await putPdsRecord(session, COLLECTION, id, { 280 282 title: fields.title ?? existing.title, 281 283 description: fields.description ?? existing.description, 282 284 status: existing.status, 285 + statusType: column?.statusType ?? "backlog", 283 286 subject: sphereOwnerDid, 284 287 assigneeDid: existing.assigneeDid ?? undefined, 285 288 updatedAt: new Date().toISOString(), ··· 343 346 const sphereId = c.var.sphereId; 344 347 const sphereVisibility = c.var.sphereVisibility; 345 348 346 - if (!getColumnBySlug(sphereId, status)) { 349 + const column = getColumnBySlug(sphereId, status); 350 + if (!column) { 347 351 return c.json({ error: "Unknown column" }, 400); 348 352 } 349 353 ··· 368 372 pdsUri = await putPdsRecord(session, STATUS_COLLECTION, statusId, { 369 373 subject: existing.pdsUri, 370 374 status, 375 + statusType: column.statusType, 371 376 }); 372 377 } 373 378 insertStatusChangeAndUpdateTask({
+20 -4
packages/kanban/src/db/operations.ts
··· 14 14 } from "./schema.ts"; 15 15 import type { KanbanColumn } from "./schema.ts"; 16 16 import { defaultColumns } from "../schemas/task.ts"; 17 + import type { StatusType } from "../schemas/status-type.ts"; 17 18 18 19 const POSITION_GAP = 1000; 19 20 ··· 51 52 sphereId, 52 53 slug: defaultColumns[i].slug, 53 54 label: defaultColumns[i].label, 55 + statusType: defaultColumns[i].statusType, 54 56 position: (i + 1) * POSITION_GAP, 55 57 }) 56 58 .run(); ··· 88 90 } 89 91 } 90 92 91 - export function insertColumn(params: { sphereId: string; label: string }): KanbanColumn { 93 + export function insertColumn(params: { 94 + sphereId: string; 95 + label: string; 96 + statusType: StatusType; 97 + }): KanbanColumn { 92 98 const db = getDb(); 93 99 const id = generateRkey(); 94 100 const slug = generateSlug(params.label, params.sphereId); ··· 101 107 const position = (lastPosition?.maxPosition ?? 0) + POSITION_GAP; 102 108 103 109 db.insert(kanbanColumns) 104 - .values({ id, sphereId: params.sphereId, slug, label: params.label, position }) 110 + .values({ 111 + id, 112 + sphereId: params.sphereId, 113 + slug, 114 + label: params.label, 115 + statusType: params.statusType, 116 + position, 117 + }) 105 118 .run(); 106 119 107 120 return db.select().from(kanbanColumns).where(eq(kanbanColumns.id, id)).get()!; 108 121 } 109 122 110 - export function updateColumnLabel(id: string, label: string): void { 111 - getDb().update(kanbanColumns).set({ label }).where(eq(kanbanColumns.id, id)).run(); 123 + export function updateColumn( 124 + id: string, 125 + fields: { label?: string; statusType?: StatusType }, 126 + ): void { 127 + getDb().update(kanbanColumns).set(fields).where(eq(kanbanColumns.id, id)).run(); 112 128 } 113 129 114 130 export function deleteColumnAndReassign(
+1
packages/kanban/src/db/schema.ts
··· 12 12 .references(() => spheres.id), 13 13 slug: text("slug").notNull(), 14 14 label: text("label").notNull(), 15 + statusType: text("status_type").notNull().default("backlog"), 15 16 position: integer("position").notNull(), 16 17 createdAt: text("created_at") 17 18 .notNull()
+10 -3
packages/kanban/src/schemas/column.ts
··· 1 1 import { z } from "zod"; 2 + import { STATUS_TYPES } from "./status-type.ts"; 2 3 3 4 export const createColumnSchema = z.object({ 4 5 label: z.string().min(1).max(50), 6 + statusType: z.enum(STATUS_TYPES), 5 7 }); 6 8 7 - export const updateColumnSchema = z.object({ 8 - label: z.string().min(1).max(50), 9 - }); 9 + export const updateColumnSchema = z 10 + .object({ 11 + label: z.string().min(1).max(50).optional(), 12 + statusType: z.enum(STATUS_TYPES).optional(), 13 + }) 14 + .refine((v) => v.label !== undefined || v.statusType !== undefined, { 15 + message: "At least one of label or statusType must be provided", 16 + }); 10 17 11 18 export const reorderColumnsSchema = z.object({ 12 19 columnIds: z.array(z.string().min(1)),
+3
packages/kanban/src/schemas/status-type.ts
··· 1 + export const STATUS_TYPES = ["backlog", "planned", "started", "completed", "canceled"] as const; 2 + 3 + export type StatusType = (typeof STATUS_TYPES)[number];
+6 -5
packages/kanban/src/schemas/task.ts
··· 1 1 import { z } from "zod"; 2 + import type { StatusType } from "./status-type.ts"; 2 3 3 4 /** Default columns seeded when a sphere's kanban board is first accessed. */ 4 5 export const defaultColumns = [ 5 - { slug: "backlog", label: "Backlog" }, 6 - { slug: "todo", label: "To do" }, 7 - { slug: "in-progress", label: "In progress" }, 8 - { slug: "done", label: "Done" }, 9 - ] as const; 6 + { slug: "backlog", label: "Backlog", statusType: "backlog" }, 7 + { slug: "todo", label: "To do", statusType: "planned" }, 8 + { slug: "in-progress", label: "In progress", statusType: "started" }, 9 + { slug: "done", label: "Done", statusType: "completed" }, 10 + ] as const satisfies ReadonlyArray<{ slug: string; label: string; statusType: StatusType }>; 10 11 11 12 export const createTaskSchema = z.object({ 12 13 title: z.string().min(1).max(200),
+2
packages/kanban/src/types.ts
··· 7 7 8 8 import type { KanbanTask, KanbanTaskComment } from "./db/schema.ts"; 9 9 import type { LabelInfo } from "@exosphere/core/sphere"; 10 + import type { StatusType } from "./schemas/status-type.ts"; 10 11 11 12 /** Column definition as returned by the API. */ 12 13 export type KanbanColumnDef = { 13 14 id: string; 14 15 slug: string; 15 16 label: string; 17 + statusType: StatusType; 16 18 position: number; 17 19 }; 18 20
+5 -4
packages/kanban/src/ui/api/columns.ts
··· 1 1 import { moduleFetch } from "@exosphere/client/module-api"; 2 2 import type { KanbanColumnDef } from "../../types.ts"; 3 + import type { StatusType } from "../../schemas/status-type.ts"; 3 4 4 5 export type { KanbanColumnDef }; 5 6 ··· 7 8 return moduleFetch<{ columns: KanbanColumnDef[] }>("/kanban/columns"); 8 9 } 9 10 10 - export function createColumn(label: string) { 11 + export function createColumn(label: string, statusType: StatusType) { 11 12 return moduleFetch<{ column: KanbanColumnDef }>("/kanban/columns", { 12 13 method: "POST", 13 14 headers: { "Content-Type": "application/json" }, 14 - body: JSON.stringify({ label }), 15 + body: JSON.stringify({ label, statusType }), 15 16 }); 16 17 } 17 18 18 - export function renameColumn(id: string, label: string) { 19 + export function updateColumnApi(id: string, fields: { label?: string; statusType?: StatusType }) { 19 20 return moduleFetch<{ column: KanbanColumnDef }>(`/kanban/columns/${encodeURIComponent(id)}`, { 20 21 method: "PUT", 21 22 headers: { "Content-Type": "application/json" }, 22 - body: JSON.stringify({ label }), 23 + body: JSON.stringify(fields), 23 24 }); 24 25 } 25 26
+70 -23
packages/kanban/src/ui/components/column-manager.tsx
··· 4 4 import * as ui from "@exosphere/client/ui.css"; 5 5 import * as kbUi from "../ui.css.ts"; 6 6 import type { KanbanColumnDef } from "../../types.ts"; 7 - import { createColumn, renameColumn, deleteColumnApi, reorderColumnsApi } from "../api/columns.ts"; 7 + import { STATUS_TYPES, type StatusType } from "../../schemas/status-type.ts"; 8 + import { 9 + createColumn, 10 + updateColumnApi, 11 + deleteColumnApi, 12 + reorderColumnsApi, 13 + } from "../api/columns.ts"; 14 + 15 + function StatusTypeSelect({ 16 + value, 17 + onChange, 18 + }: { 19 + value: StatusType; 20 + onChange: (v: StatusType) => void; 21 + }) { 22 + return ( 23 + <select 24 + class={kbUi.statusSelect} 25 + value={value} 26 + onChange={(e) => onChange((e.target as HTMLSelectElement).value as StatusType)} 27 + > 28 + {STATUS_TYPES.map((t) => ( 29 + <option key={t} value={t}> 30 + {t.charAt(0).toUpperCase() + t.slice(1)} 31 + </option> 32 + ))} 33 + </select> 34 + ); 35 + } 8 36 9 37 export function ColumnManager({ 10 38 columns, ··· 14 42 onChanged: () => void; 15 43 }) { 16 44 const newLabel = useSignal(""); 45 + const newStatusType = useSignal<StatusType>("backlog"); 17 46 const adding = useSignal(false); 18 47 const editingId = useSignal<string | null>(null); 19 48 const editLabel = useSignal(""); 49 + const editStatusType = useSignal<StatusType>("backlog"); 20 50 const deletingId = useSignal<string | null>(null); 21 51 const reassignSlug = useSignal(""); 22 52 const busy = useSignal(false); ··· 25 55 if (!newLabel.value.trim() || adding.value) return; 26 56 adding.value = true; 27 57 try { 28 - await createColumn(newLabel.value.trim()); 58 + await createColumn(newLabel.value.trim(), newStatusType.value); 29 59 newLabel.value = ""; 60 + newStatusType.value = "backlog"; 30 61 onChanged(); 31 62 } catch (err) { 32 63 console.error("Failed to add column:", err); ··· 35 66 } 36 67 }; 37 68 38 - const handleRename = async (id: string) => { 39 - if (!editLabel.value.trim() || busy.value) return; 69 + const handleSaveEdit = async (col: KanbanColumnDef) => { 70 + const label = editLabel.value.trim(); 71 + if (!label || busy.value) return; 72 + if (label === col.label && editStatusType.value === col.statusType) { 73 + editingId.value = null; 74 + return; 75 + } 40 76 busy.value = true; 41 77 try { 42 - await renameColumn(id, editLabel.value.trim()); 78 + await updateColumnApi(col.id, { label, statusType: editStatusType.value }); 43 79 editingId.value = null; 44 80 onChanged(); 45 81 } catch (err) { 46 - console.error("Failed to rename column:", err); 82 + console.error("Failed to update column:", err); 47 83 } finally { 48 84 busy.value = false; 49 85 } ··· 125 161 const startEdit = (col: KanbanColumnDef) => { 126 162 editingId.value = col.id; 127 163 editLabel.value = col.label; 164 + editStatusType.value = col.statusType; 128 165 deletingId.value = null; 129 166 }; 130 167 ··· 157 194 <GripVertical size={16} class={kbUi.dragHandle} /> 158 195 159 196 {editingId.value === col.id ? ( 160 - <input 161 - class={ui.input} 162 - type="text" 163 - maxLength={50} 164 - value={editLabel.value} 165 - onInput={(e) => (editLabel.value = (e.target as HTMLInputElement).value)} 166 - onKeyDown={(e) => { 167 - if (e.key === "Enter") handleRename(col.id); 168 - if (e.key === "Escape") editingId.value = null; 169 - }} 170 - autoFocus 171 - /> 197 + <> 198 + <input 199 + class={ui.input} 200 + type="text" 201 + maxLength={50} 202 + value={editLabel.value} 203 + onInput={(e) => (editLabel.value = (e.target as HTMLInputElement).value)} 204 + onKeyDown={(e) => { 205 + if (e.key === "Enter") handleSaveEdit(col); 206 + if (e.key === "Escape") editingId.value = null; 207 + }} 208 + autoFocus 209 + /> 210 + <StatusTypeSelect 211 + value={editStatusType.value} 212 + onChange={(v) => (editStatusType.value = v)} 213 + /> 214 + </> 172 215 ) : ( 173 - <span class={kbUi.columnManagerLabel}> 174 - {col.label} <span class={kbUi.columnManagerSlug}>({col.slug})</span> 175 - </span> 216 + <> 217 + <span class={kbUi.columnManagerLabel}> 218 + {col.label} <span class={kbUi.columnManagerSlug}>({col.slug})</span> 219 + </span> 220 + <span class={kbUi.statusTypeBadge[col.statusType]}>{col.statusType}</span> 221 + </> 176 222 )} 177 223 178 224 {editingId.value === col.id ? ( ··· 180 226 <button 181 227 class={ui.buttonCompact} 182 228 disabled={busy.value || !editLabel.value.trim()} 183 - onClick={() => handleRename(col.id)} 229 + onClick={() => handleSaveEdit(col)} 184 230 > 185 231 Save 186 232 </button> ··· 191 237 ) : ( 192 238 <> 193 239 <button class={ui.buttonInline} onClick={() => startEdit(col)}> 194 - Rename 240 + Edit 195 241 </button> 196 242 {columns.length > 1 && ( 197 243 <button class={ui.buttonDangerInline} onClick={() => startDelete(col)}> ··· 248 294 if (e.key === "Enter") handleAdd(); 249 295 }} 250 296 /> 297 + <StatusTypeSelect value={newStatusType.value} onChange={(v) => (newStatusType.value = v)} /> 251 298 <button 252 299 class={ui.buttonCompact} 253 300 disabled={adding.value || !newLabel.value.trim()}
+20 -1
packages/kanban/src/ui/ui.css.ts
··· 1 - import { globalStyle, style } from "@vanilla-extract/css"; 1 + import { globalStyle, style, styleVariants } from "@vanilla-extract/css"; 2 2 import { vars } from "@exosphere/client/theme.css"; 3 3 4 4 // ---- Board layout ---- ··· 274 274 export const columnManagerSlug = style({ 275 275 fontSize: "0.75rem", 276 276 color: vars.color.textMuted, 277 + }); 278 + 279 + const statusTypeBadgeBase = style({ 280 + fontSize: "0.6875rem", 281 + fontWeight: 600, 282 + letterSpacing: "0.04em", 283 + textTransform: "uppercase", 284 + borderRadius: vars.radius.sm, 285 + paddingBlock: "2px", 286 + paddingInline: vars.space.xs, 287 + flexShrink: 0, 288 + }); 289 + 290 + export const statusTypeBadge = styleVariants({ 291 + backlog: [statusTypeBadgeBase, { color: vars.color.textMuted, border: `1px solid ${vars.color.border}` }], 292 + planned: [statusTypeBadgeBase, { color: vars.color.text, border: `1px solid ${vars.color.border}` }], 293 + started: [statusTypeBadgeBase, { color: vars.color.warning, border: `1px solid ${vars.color.warning}` }], 294 + completed: [statusTypeBadgeBase, { color: vars.color.success, border: `1px solid ${vars.color.success}` }], 295 + canceled: [statusTypeBadgeBase, { color: vars.color.danger, border: `1px solid ${vars.color.danger}` }], 277 296 }); 278 297 279 298 export const dragHandle = style({