stagent 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +11 -0
  2. package/dist/cli.js +39 -10
  3. package/drizzle.config.ts +3 -1
  4. package/package.json +3 -1
  5. package/src/app/api/book/bookmarks/route.ts +73 -0
  6. package/src/app/api/book/progress/route.ts +79 -0
  7. package/src/app/api/book/regenerate/route.ts +111 -0
  8. package/src/app/api/book/stage/route.ts +13 -0
  9. package/src/app/api/chat/conversations/[id]/respond/route.ts +19 -20
  10. package/src/app/api/chat/conversations/[id]/route.ts +2 -1
  11. package/src/app/api/documents/[id]/route.ts +34 -2
  12. package/src/app/api/documents/route.ts +91 -0
  13. package/src/app/api/settings/runtime/route.ts +46 -0
  14. package/src/app/book/page.tsx +14 -0
  15. package/src/app/chat/page.tsx +7 -1
  16. package/src/app/globals.css +375 -0
  17. package/src/app/projects/[id]/page.tsx +31 -6
  18. package/src/app/settings/page.tsx +2 -0
  19. package/src/app/{playbook → user-guide}/[slug]/page.tsx +12 -2
  20. package/src/app/{playbook → user-guide}/page.tsx +2 -2
  21. package/src/app/workflows/[id]/page.tsx +28 -2
  22. package/src/components/book/book-reader.tsx +801 -0
  23. package/src/components/book/chapter-generation-bar.tsx +109 -0
  24. package/src/components/book/content-blocks.tsx +432 -0
  25. package/src/components/book/path-progress.tsx +33 -0
  26. package/src/components/book/path-selector.tsx +42 -0
  27. package/src/components/book/try-it-now.tsx +164 -0
  28. package/src/components/chat/chat-activity-indicator.tsx +92 -0
  29. package/src/components/chat/chat-message-list.tsx +3 -0
  30. package/src/components/chat/chat-message.tsx +22 -6
  31. package/src/components/chat/chat-permission-request.tsx +5 -1
  32. package/src/components/chat/chat-question.tsx +3 -0
  33. package/src/components/chat/chat-shell.tsx +130 -19
  34. package/src/components/chat/conversation-list.tsx +8 -2
  35. package/src/components/playbook/adoption-heatmap.tsx +1 -1
  36. package/src/components/playbook/journey-card.tsx +1 -1
  37. package/src/components/playbook/playbook-card.tsx +1 -1
  38. package/src/components/playbook/playbook-detail-view.tsx +15 -5
  39. package/src/components/playbook/playbook-homepage.tsx +1 -1
  40. package/src/components/playbook/playbook-updated-badge.tsx +1 -1
  41. package/src/components/projects/project-detail.tsx +147 -27
  42. package/src/components/projects/project-form-sheet.tsx +6 -2
  43. package/src/components/projects/project-list.tsx +1 -1
  44. package/src/components/settings/runtime-timeout-section.tsx +170 -0
  45. package/src/components/shared/app-sidebar.tsx +7 -1
  46. package/src/components/shared/command-palette.tsx +4 -4
  47. package/src/hooks/use-chapter-generation.ts +255 -0
  48. package/src/lib/agents/claude-agent.ts +12 -6
  49. package/src/lib/agents/runtime/claude.ts +29 -3
  50. package/src/lib/book/chapter-generator.ts +193 -0
  51. package/src/lib/book/chapter-mapping.ts +91 -0
  52. package/src/lib/book/content.ts +251 -0
  53. package/src/lib/book/markdown-parser.ts +317 -0
  54. package/src/lib/book/reading-paths.ts +82 -0
  55. package/src/lib/book/types.ts +152 -0
  56. package/src/lib/book/update-detector.ts +157 -0
  57. package/src/lib/chat/codex-engine.ts +537 -0
  58. package/src/lib/chat/context-builder.ts +18 -4
  59. package/src/lib/chat/engine.ts +116 -39
  60. package/src/lib/chat/model-discovery.ts +13 -5
  61. package/src/lib/chat/permission-bridge.ts +14 -2
  62. package/src/lib/chat/stagent-tools.ts +2 -0
  63. package/src/lib/chat/system-prompt.ts +16 -1
  64. package/src/lib/chat/tools/chat-history-tools.ts +177 -0
  65. package/src/lib/chat/tools/document-tools.ts +204 -0
  66. package/src/lib/chat/tools/settings-tools.ts +30 -3
  67. package/src/lib/chat/types.ts +8 -1
  68. package/src/lib/constants/settings.ts +2 -0
  69. package/src/lib/data/chat.ts +83 -2
  70. package/src/lib/data/clear.ts +8 -0
  71. package/src/lib/db/bootstrap.ts +24 -0
  72. package/src/lib/db/schema.ts +32 -0
  73. package/src/lib/docs/types.ts +9 -0
  74. /package/src/app/api/{playbook → user-guide}/status/route.ts +0 -0
@@ -0,0 +1,46 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getSetting, setSetting } from "@/lib/settings/helpers";
3
+ import { SETTINGS_KEYS } from "@/lib/constants/settings";
4
+
5
+ export async function GET() {
6
+ const sdkTimeoutSeconds = await getSetting(SETTINGS_KEYS.SDK_TIMEOUT_SECONDS);
7
+ const maxTurns = await getSetting(SETTINGS_KEYS.MAX_TURNS);
8
+ return NextResponse.json({
9
+ sdkTimeoutSeconds: sdkTimeoutSeconds ?? "60",
10
+ maxTurns: maxTurns ?? "10",
11
+ });
12
+ }
13
+
14
+ export async function POST(req: NextRequest) {
15
+ const body = await req.json();
16
+
17
+ if (body.sdkTimeoutSeconds !== undefined) {
18
+ const seconds = parseInt(body.sdkTimeoutSeconds, 10);
19
+ if (isNaN(seconds) || seconds < 10 || seconds > 300) {
20
+ return NextResponse.json(
21
+ { error: "sdkTimeoutSeconds must be between 10 and 300" },
22
+ { status: 400 }
23
+ );
24
+ }
25
+ await setSetting(SETTINGS_KEYS.SDK_TIMEOUT_SECONDS, String(seconds));
26
+ }
27
+
28
+ if (body.maxTurns !== undefined) {
29
+ const turns = parseInt(body.maxTurns, 10);
30
+ if (isNaN(turns) || turns < 1 || turns > 50) {
31
+ return NextResponse.json(
32
+ { error: "maxTurns must be between 1 and 50" },
33
+ { status: 400 }
34
+ );
35
+ }
36
+ await setSetting(SETTINGS_KEYS.MAX_TURNS, String(turns));
37
+ }
38
+
39
+ const sdkTimeoutSeconds = await getSetting(SETTINGS_KEYS.SDK_TIMEOUT_SECONDS);
40
+ const maxTurns = await getSetting(SETTINGS_KEYS.MAX_TURNS);
41
+
42
+ return NextResponse.json({
43
+ sdkTimeoutSeconds: sdkTimeoutSeconds ?? "60",
44
+ maxTurns: maxTurns ?? "10",
45
+ });
46
+ }
@@ -0,0 +1,14 @@
1
+ import { PageShell } from "@/components/shared/page-shell";
2
+ import { BookReader } from "@/components/book/book-reader";
3
+ import { getBook } from "@/lib/book/content";
4
+
5
+ export const dynamic = "force-dynamic";
6
+
7
+ export default function BookPage() {
8
+ const book = getBook();
9
+ return (
10
+ <PageShell fullBleed>
11
+ <BookReader chapters={book.chapters} />
12
+ </PageShell>
13
+ );
14
+ }
@@ -4,7 +4,12 @@ import { ChatShell } from "@/components/chat/chat-shell";
4
4
 
5
5
  export const dynamic = "force-dynamic";
6
6
 
7
- export default async function ChatPage() {
7
+ export default async function ChatPage({
8
+ searchParams,
9
+ }: {
10
+ searchParams: Promise<{ c?: string }>;
11
+ }) {
12
+ const params = await searchParams;
8
13
  const [conversations, promptCategories] = await Promise.all([
9
14
  listConversations({ status: "active" }),
10
15
  getPromptCategories(),
@@ -14,6 +19,7 @@ export default async function ChatPage() {
14
19
  <ChatShell
15
20
  initialConversations={conversations}
16
21
  promptCategories={promptCategories}
22
+ initialActiveId={params.c ?? null}
17
23
  />
18
24
  );
19
25
  }
@@ -474,6 +474,25 @@
474
474
  100% { transform: translateX(100%); }
475
475
  }
476
476
 
477
+ /* Fade-in for animated text replacement */
478
+ @keyframes fade-in {
479
+ from { opacity: 0; transform: translateY(2px); }
480
+ to { opacity: 1; transform: translateY(0); }
481
+ }
482
+ .animate-fade-in {
483
+ animation: fade-in 0.3s ease-out;
484
+ }
485
+
486
+ /* Pulse-slide for indeterminate progress bars */
487
+ @keyframes pulse-slide {
488
+ 0% { transform: translateX(-100%); opacity: 0.4; }
489
+ 50% { transform: translateX(100%); opacity: 1; }
490
+ 100% { transform: translateX(300%); opacity: 0.4; }
491
+ }
492
+ .animate-pulse-slide {
493
+ animation: pulse-slide 2s ease-in-out infinite;
494
+ }
495
+
477
496
  /* Card exit (ghost card deletion) */
478
497
  @keyframes card-exit {
479
498
  0% {
@@ -545,6 +564,362 @@ body {
545
564
  outline-offset: 2px;
546
565
  }
547
566
 
567
+ /* ─── Book Reader Container (theme backgrounds) ─── */
568
+ .book-reader-container {
569
+ background: var(--background);
570
+ color: var(--foreground);
571
+ }
572
+ [data-book-theme="sepia"].book-reader-container {
573
+ background: oklch(0.95 0.03 75);
574
+ color: oklch(0.25 0.04 60);
575
+ }
576
+ [data-book-theme="dark"].book-reader-container {
577
+ background: oklch(0.14 0.02 250);
578
+ color: oklch(0.88 0.01 250);
579
+ }
580
+
581
+ /* Theme preview buttons in settings */
582
+ .book-theme-preview-sepia:not([class*="border-primary"]) {
583
+ background: oklch(0.95 0.03 75);
584
+ }
585
+ .book-theme-preview-dark:not([class*="border-primary"]) {
586
+ background: oklch(0.18 0.02 250);
587
+ color: oklch(0.88 0.01 250);
588
+ }
589
+
590
+ /* ─── Book Reader Prose ─── */
591
+ .book-prose {
592
+ & p { margin-bottom: 1.25em; }
593
+ & blockquote {
594
+ border-left: 3px solid var(--border);
595
+ padding-left: 1em;
596
+ margin: 1.5em 0;
597
+ color: var(--muted-foreground);
598
+ font-style: italic;
599
+ }
600
+ & h2 {
601
+ font-size: 1.5em;
602
+ font-weight: 700;
603
+ margin-top: 2.5em;
604
+ margin-bottom: 0.75em;
605
+ letter-spacing: -0.01em;
606
+ }
607
+ & h3 {
608
+ font-size: 1.25em;
609
+ font-weight: 600;
610
+ margin-top: 2em;
611
+ margin-bottom: 0.75em;
612
+ }
613
+ & h4 {
614
+ font-size: 1.1em;
615
+ font-weight: 600;
616
+ margin-top: 1.75em;
617
+ margin-bottom: 0.5em;
618
+ }
619
+ & ul, & ol {
620
+ margin: 1em 0;
621
+ padding-left: 1.5em;
622
+ }
623
+ & ul { list-style-type: disc; }
624
+ & ol { list-style-type: decimal; }
625
+ & li { margin-bottom: 0.4em; }
626
+ & li > ul, & li > ol {
627
+ margin-top: 0.25em;
628
+ margin-bottom: 0.25em;
629
+ }
630
+ & strong { font-weight: 650; }
631
+ & em { font-style: italic; }
632
+ & code {
633
+ font-family: var(--font-mono);
634
+ font-size: 0.875em;
635
+ padding: 0.15em 0.35em;
636
+ border-radius: 4px;
637
+ background: var(--muted);
638
+ }
639
+ & a {
640
+ color: var(--primary);
641
+ text-decoration: underline;
642
+ text-underline-offset: 2px;
643
+ transition: opacity 0.15s;
644
+ &:hover { opacity: 0.8; }
645
+ }
646
+ & hr {
647
+ border: none;
648
+ border-top: 1px solid var(--border);
649
+ margin: 2em 0;
650
+ }
651
+ /* Tables */
652
+ & table {
653
+ width: 100%;
654
+ border-collapse: collapse;
655
+ margin: 1.5em 0;
656
+ font-size: 0.9em;
657
+ }
658
+ & thead th {
659
+ text-align: left;
660
+ font-weight: 600;
661
+ padding: 0.5em 0.75em;
662
+ border-bottom: 2px solid var(--border);
663
+ }
664
+ & tbody td {
665
+ padding: 0.5em 0.75em;
666
+ border-bottom: 1px solid var(--border);
667
+ vertical-align: top;
668
+ }
669
+ & tbody tr:last-child td { border-bottom: none; }
670
+ /* Images within markdown */
671
+ & img {
672
+ max-width: 100%;
673
+ border-radius: 8px;
674
+ margin: 1.5em auto;
675
+ display: block;
676
+ }
677
+ }
678
+
679
+ /* ─── Book Code Blocks (syntax-highlighted) ─── */
680
+ .book-code-header {
681
+ background: var(--muted);
682
+ border: 1px solid var(--border);
683
+ border-bottom: none;
684
+ }
685
+ .book-code-pre {
686
+ background: var(--surface-3);
687
+ border: 1px solid var(--border);
688
+ font-family: var(--font-mono);
689
+ }
690
+
691
+ /* sugar-high token colors — light theme */
692
+ .book-code-pre code {
693
+ --sh-class: oklch(0.55 0.22 260); /* indigo - classes */
694
+ --sh-identifier: var(--foreground); /* default text */
695
+ --sh-sign: oklch(0.45 0.03 250); /* punctuation */
696
+ --sh-property: oklch(0.50 0.17 260); /* properties */
697
+ --sh-entity: oklch(0.52 0.17 165); /* entities — emerald */
698
+ --sh-jsxliterals: oklch(0.55 0.19 45); /* JSX text — warm */
699
+ --sh-string: oklch(0.52 0.17 165); /* strings — emerald */
700
+ --sh-keyword: oklch(0.55 0.22 300); /* keywords — magenta */
701
+ --sh-comment: oklch(0.55 0.02 250); /* comments — muted */
702
+ }
703
+
704
+ /* sugar-high token colors — sepia reader theme */
705
+ [data-book-theme="sepia"] .book-code-header {
706
+ background: oklch(0.93 0.03 75);
707
+ border-color: oklch(0.85 0.04 75);
708
+ }
709
+ [data-book-theme="sepia"] .book-code-pre {
710
+ background: oklch(0.96 0.02 75);
711
+ border-color: oklch(0.85 0.04 75);
712
+ }
713
+
714
+ /* sugar-high token colors — dark reader theme */
715
+ [data-book-theme="dark"] .book-code-header {
716
+ background: oklch(0.20 0.02 250);
717
+ border-color: oklch(0.28 0.02 250);
718
+ }
719
+ [data-book-theme="dark"] .book-code-pre {
720
+ background: oklch(0.14 0.02 250);
721
+ border-color: oklch(0.28 0.02 250);
722
+ }
723
+ [data-book-theme="dark"] .book-code-pre code {
724
+ --sh-class: oklch(0.72 0.18 260);
725
+ --sh-identifier: oklch(0.88 0.01 250);
726
+ --sh-sign: oklch(0.60 0.02 250);
727
+ --sh-property: oklch(0.70 0.16 260);
728
+ --sh-entity: oklch(0.68 0.17 165);
729
+ --sh-jsxliterals: oklch(0.70 0.16 45);
730
+ --sh-string: oklch(0.68 0.17 165);
731
+ --sh-keyword: oklch(0.72 0.18 300);
732
+ --sh-comment: oklch(0.48 0.02 250);
733
+ }
734
+
735
+ /* system dark mode overrides for code blocks */
736
+ .dark .book-code-header {
737
+ background: oklch(0.20 0.02 250);
738
+ border-color: oklch(0.28 0.02 250);
739
+ }
740
+ .dark .book-code-pre {
741
+ background: oklch(0.14 0.02 250);
742
+ border-color: oklch(0.28 0.02 250);
743
+ }
744
+ .dark .book-code-pre code {
745
+ --sh-class: oklch(0.72 0.18 260);
746
+ --sh-identifier: oklch(0.88 0.01 250);
747
+ --sh-sign: oklch(0.60 0.02 250);
748
+ --sh-property: oklch(0.70 0.16 260);
749
+ --sh-entity: oklch(0.68 0.17 165);
750
+ --sh-jsxliterals: oklch(0.70 0.16 45);
751
+ --sh-string: oklch(0.68 0.17 165);
752
+ --sh-keyword: oklch(0.72 0.18 300);
753
+ --sh-comment: oklch(0.48 0.02 250);
754
+ }
755
+
756
+ /* ─── Book Image Blocks ─── */
757
+ .book-image {
758
+ border: 1px solid var(--border);
759
+ }
760
+ .book-image-breakout {
761
+ width: 170%;
762
+ max-width: min(170%, calc(100vw - 16rem));
763
+ margin-left: -35%;
764
+ }
765
+ .book-image-breakout figcaption {
766
+ width: 58.82%;
767
+ margin-left: 20.59%;
768
+ }
769
+ .book-image-skeleton {
770
+ background: var(--muted);
771
+ border: 1px solid var(--border);
772
+ }
773
+ [data-book-theme="sepia"] .book-image {
774
+ border-color: oklch(0.85 0.04 75);
775
+ }
776
+ [data-book-theme="sepia"] .book-image-skeleton {
777
+ background: oklch(0.93 0.03 75);
778
+ border-color: oklch(0.85 0.04 75);
779
+ }
780
+ [data-book-theme="dark"] .book-image {
781
+ border-color: oklch(0.28 0.02 250);
782
+ }
783
+
784
+ /* ─── Book Callout Blocks ─── */
785
+ .book-callout-tip {
786
+ border-left-color: oklch(0.52 0.17 165);
787
+ background: oklch(0.52 0.17 165 / 0.05);
788
+ }
789
+ .book-callout-tip .book-callout-icon { color: oklch(0.52 0.17 165); }
790
+ .book-callout-tip .book-callout-title { color: oklch(0.42 0.12 165); }
791
+
792
+ .book-callout-warning {
793
+ border-left-color: oklch(0.65 0.18 75);
794
+ background: oklch(0.65 0.18 75 / 0.05);
795
+ }
796
+ .book-callout-warning .book-callout-icon { color: oklch(0.65 0.18 75); }
797
+ .book-callout-warning .book-callout-title { color: oklch(0.55 0.14 75); }
798
+
799
+ .book-callout-info {
800
+ border-left-color: oklch(0.55 0.20 260);
801
+ background: oklch(0.55 0.20 260 / 0.05);
802
+ }
803
+ .book-callout-info .book-callout-icon { color: oklch(0.55 0.20 260); }
804
+ .book-callout-info .book-callout-title { color: oklch(0.45 0.16 260); }
805
+
806
+ .book-callout-lesson {
807
+ border-left-color: oklch(0.55 0.20 300);
808
+ background: oklch(0.55 0.20 300 / 0.05);
809
+ }
810
+ .book-callout-lesson .book-callout-icon { color: oklch(0.55 0.20 300); }
811
+ .book-callout-lesson .book-callout-title { color: oklch(0.45 0.16 300); }
812
+
813
+
814
+ /* callout body inline code */
815
+ .book-callout-body code {
816
+ background: oklch(0 0 0 / 0.05);
817
+ }
818
+
819
+ /* sepia callout overrides */
820
+ [data-book-theme="sepia"] .book-callout-tip { background: oklch(0.52 0.17 165 / 0.08); }
821
+ [data-book-theme="sepia"] .book-callout-warning { background: oklch(0.65 0.18 75 / 0.08); }
822
+ [data-book-theme="sepia"] .book-callout-info { background: oklch(0.55 0.20 260 / 0.08); }
823
+ [data-book-theme="sepia"] .book-callout-lesson { background: oklch(0.55 0.20 300 / 0.08); }
824
+ [data-book-theme="sepia"] .book-callout-body code { background: oklch(0 0 0 / 0.06); }
825
+
826
+ /* dark callout overrides */
827
+ [data-book-theme="dark"] .book-callout-tip {
828
+ background: oklch(0.52 0.17 165 / 0.10);
829
+ }
830
+ [data-book-theme="dark"] .book-callout-tip .book-callout-icon { color: oklch(0.68 0.17 165); }
831
+ [data-book-theme="dark"] .book-callout-tip .book-callout-title { color: oklch(0.72 0.14 165); }
832
+
833
+ [data-book-theme="dark"] .book-callout-warning {
834
+ background: oklch(0.65 0.18 75 / 0.10);
835
+ }
836
+ [data-book-theme="dark"] .book-callout-warning .book-callout-icon { color: oklch(0.75 0.18 75); }
837
+ [data-book-theme="dark"] .book-callout-warning .book-callout-title { color: oklch(0.80 0.14 75); }
838
+
839
+ [data-book-theme="dark"] .book-callout-info {
840
+ background: oklch(0.55 0.20 260 / 0.10);
841
+ }
842
+ [data-book-theme="dark"] .book-callout-info .book-callout-icon { color: oklch(0.70 0.18 260); }
843
+ [data-book-theme="dark"] .book-callout-info .book-callout-title { color: oklch(0.75 0.14 260); }
844
+
845
+ [data-book-theme="dark"] .book-callout-lesson {
846
+ background: oklch(0.55 0.20 300 / 0.10);
847
+ }
848
+ [data-book-theme="dark"] .book-callout-lesson .book-callout-icon { color: oklch(0.70 0.18 300); }
849
+ [data-book-theme="dark"] .book-callout-lesson .book-callout-title { color: oklch(0.75 0.14 300); }
850
+
851
+
852
+ [data-book-theme="dark"] .book-callout-body code { background: oklch(1 0 0 / 0.10); }
853
+
854
+ /* system dark mode callout overrides */
855
+ .dark .book-callout-tip .book-callout-icon { color: oklch(0.68 0.17 165); }
856
+ .dark .book-callout-tip .book-callout-title { color: oklch(0.72 0.14 165); }
857
+ .dark .book-callout-warning .book-callout-icon { color: oklch(0.75 0.18 75); }
858
+ .dark .book-callout-warning .book-callout-title { color: oklch(0.80 0.14 75); }
859
+ .dark .book-callout-info .book-callout-icon { color: oklch(0.70 0.18 260); }
860
+ .dark .book-callout-info .book-callout-title { color: oklch(0.75 0.14 260); }
861
+ .dark .book-callout-lesson .book-callout-icon { color: oklch(0.70 0.18 300); }
862
+ .dark .book-callout-lesson .book-callout-title { color: oklch(0.75 0.14 300); }
863
+ .dark .book-callout-body code { background: oklch(1 0 0 / 0.10); }
864
+
865
+ /* ─── Book Interactive Blocks ─── */
866
+ .book-interactive-link {
867
+ border-color: var(--border);
868
+ }
869
+ .book-interactive-link:hover {
870
+ border-color: var(--primary);
871
+ background: oklch(0.50 0.20 260 / 0.05);
872
+ }
873
+ .book-interactive-collapsible {
874
+ border-color: var(--border);
875
+ }
876
+ .book-interactive-quiz {
877
+ border-color: var(--border);
878
+ }
879
+
880
+ /* quiz answer states */
881
+ .book-quiz-correct {
882
+ border-color: oklch(0.52 0.17 165 / 0.5);
883
+ background: oklch(0.52 0.17 165 / 0.05);
884
+ color: oklch(0.42 0.12 165);
885
+ }
886
+ .book-quiz-wrong {
887
+ border-color: oklch(0.55 0.22 25 / 0.5);
888
+ background: oklch(0.55 0.22 25 / 0.05);
889
+ color: oklch(0.45 0.18 25);
890
+ }
891
+ .book-quiz-explanation-correct {
892
+ border-left-color: oklch(0.52 0.17 165 / 0.3);
893
+ background: oklch(0.52 0.17 165 / 0.05);
894
+ }
895
+ .book-quiz-explanation-wrong {
896
+ border-left-color: oklch(0.65 0.18 75 / 0.3);
897
+ background: oklch(0.65 0.18 75 / 0.05);
898
+ }
899
+
900
+ /* dark reader quiz overrides */
901
+ [data-book-theme="dark"] .book-quiz-correct {
902
+ color: oklch(0.72 0.14 165);
903
+ }
904
+ [data-book-theme="dark"] .book-quiz-wrong {
905
+ color: oklch(0.72 0.16 25);
906
+ }
907
+ [data-book-theme="dark"] .book-interactive-link:hover {
908
+ background: oklch(0.65 0.20 260 / 0.10);
909
+ }
910
+
911
+ /* dark reader interactive borders */
912
+ [data-book-theme="dark"] .book-interactive-link,
913
+ [data-book-theme="dark"] .book-interactive-collapsible,
914
+ [data-book-theme="dark"] .book-interactive-quiz {
915
+ border-color: oklch(0.28 0.02 250);
916
+ }
917
+
918
+ /* sepia interactive backgrounds */
919
+ [data-book-theme="sepia"] .book-interactive-link:hover {
920
+ background: oklch(0.50 0.20 260 / 0.06);
921
+ }
922
+
548
923
  /* Reduced motion — disable all animations and transitions */
549
924
  @media (prefers-reduced-motion: reduce) {
550
925
  *,
@@ -1,7 +1,7 @@
1
1
  import { notFound } from "next/navigation";
2
2
  import { db } from "@/lib/db";
3
- import { projects, tasks } from "@/lib/db/schema";
4
- import { eq, count } from "drizzle-orm";
3
+ import { projects, tasks, workflows } from "@/lib/db/schema";
4
+ import { eq, count, getTableColumns } from "drizzle-orm";
5
5
  import { Badge } from "@/components/ui/badge";
6
6
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
7
7
  import { COLUMN_ORDER } from "@/lib/constants/task-status";
@@ -28,26 +28,41 @@ export default async function ProjectDetailPage({
28
28
  if (!project) notFound();
29
29
 
30
30
  const projectTasks = await db
31
- .select()
31
+ .select({
32
+ ...getTableColumns(tasks),
33
+ workflowName: workflows.name,
34
+ workflowStatus: workflows.status,
35
+ })
32
36
  .from(tasks)
37
+ .leftJoin(workflows, eq(tasks.workflowId, workflows.id))
33
38
  .where(eq(tasks.projectId, id))
34
39
  .orderBy(tasks.priority, tasks.createdAt);
35
40
 
36
- // Status breakdown
41
+ // Status breakdown (standalone tasks only for headline metrics)
37
42
  const statusCounts: Record<string, number> = {};
43
+ const standaloneForCounts = projectTasks.filter((t) => !t.workflowId);
38
44
  for (const status of COLUMN_ORDER) {
39
- statusCounts[status] = projectTasks.filter((t) => t.status === status).length;
45
+ statusCounts[status] = standaloneForCounts.filter((t) => t.status === status).length;
40
46
  }
41
47
 
42
48
  const completionTrend = await getProjectCompletionTrend(id, 14);
43
- const totalTasks = projectTasks.length;
49
+ const totalTasks = standaloneForCounts.length;
50
+
51
+ const standaloneTasks = projectTasks.filter((t) => !t.workflowId);
52
+ const workflowTasks = projectTasks.filter((t) => t.workflowId);
44
53
 
45
54
  const serializedTasks = projectTasks.map((t) => ({
46
55
  ...t,
47
56
  createdAt: t.createdAt.toISOString(),
48
57
  updatedAt: t.updatedAt.toISOString(),
58
+ workflowName: t.workflowName ?? null,
59
+ workflowStatus: t.workflowStatus ?? null,
49
60
  }));
50
61
 
62
+ const standaloneCount = standaloneTasks.length;
63
+ const workflowCount = workflowTasks.length;
64
+ const workflowGroupCount = new Set(workflowTasks.map((t) => t.workflowId)).size;
65
+
51
66
  const statusVariant: Record<string, "default" | "secondary" | "outline"> = {
52
67
  active: "default",
53
68
  paused: "secondary",
@@ -129,6 +144,16 @@ export default async function ProjectDetailPage({
129
144
  </div>
130
145
  )}
131
146
 
147
+ {/* Task count summary */}
148
+ {(standaloneCount > 0 || workflowCount > 0) && (
149
+ <p className="text-xs text-muted-foreground mb-4">
150
+ {standaloneCount} standalone task{standaloneCount !== 1 ? "s" : ""}
151
+ {workflowCount > 0 && (
152
+ <> &middot; {workflowCount} workflow task{workflowCount !== 1 ? "s" : ""} across {workflowGroupCount} workflow{workflowGroupCount !== 1 ? "s" : ""}</>
153
+ )}
154
+ </p>
155
+ )}
156
+
132
157
  <ProjectDetailClient tasks={serializedTasks} projectId={id} />
133
158
  </PageShell>
134
159
  );
@@ -4,6 +4,7 @@ import { PermissionsSections } from "@/components/settings/permissions-sections"
4
4
  import { DataManagementSection } from "@/components/settings/data-management-section";
5
5
  import { BudgetGuardrailsSection } from "@/components/settings/budget-guardrails-section";
6
6
  import { ChatSettingsSection } from "@/components/settings/chat-settings-section";
7
+ import { RuntimeTimeoutSection } from "@/components/settings/runtime-timeout-section";
7
8
  import { PageShell } from "@/components/shared/page-shell";
8
9
 
9
10
  export const dynamic = "force-dynamic";
@@ -15,6 +16,7 @@ export default function SettingsPage() {
15
16
  <AuthConfigSection />
16
17
  <OpenAIRuntimeSection />
17
18
  <ChatSettingsSection />
19
+ <RuntimeTimeoutSection />
18
20
  <BudgetGuardrailsSection />
19
21
  <PermissionsSections />
20
22
  <DataManagementSection />
@@ -3,6 +3,8 @@ import { getDocBySlug, getManifest } from "@/lib/docs/reader";
3
3
  import { getAdoptionMap } from "@/lib/docs/adoption";
4
4
  import { PageShell } from "@/components/shared/page-shell";
5
5
  import { PlaybookDetailView } from "@/components/playbook/playbook-detail-view";
6
+ import { getChapterForDoc } from "@/lib/book/chapter-mapping";
7
+ import { getChapter } from "@/lib/book/content";
6
8
 
7
9
  export const dynamic = "force-dynamic";
8
10
 
@@ -15,8 +17,8 @@ export async function generateMetadata({ params }: PlaybookDetailProps) {
15
17
  const doc = getDocBySlug(slug);
16
18
  return {
17
19
  title: doc
18
- ? `${(doc.frontmatter.title as string) || slug} | Playbook | Stagent`
19
- : "Not Found | Playbook",
20
+ ? `${(doc.frontmatter.title as string) || slug} | User Guide | Stagent`
21
+ : "Not Found | User Guide",
20
22
  };
21
23
  }
22
24
 
@@ -54,6 +56,13 @@ export default async function PlaybookDetailPage({
54
56
 
55
57
  const adoption = Object.fromEntries(adoptionMap);
56
58
 
59
+ // Find related book chapter for "Read the story" breadcrumb
60
+ const bookChapterId = getChapterForDoc(slug);
61
+ const bookChapter = bookChapterId ? getChapter(bookChapterId) : undefined;
62
+ const bookChapterLink = bookChapter && bookChapterId
63
+ ? { title: `Ch. ${bookChapter.number}: ${bookChapter.title}`, id: bookChapterId }
64
+ : undefined;
65
+
57
66
  // Collect all known doc slugs so markdown links resolve correctly
58
67
  const allSlugs = [
59
68
  ...manifest.sections.map((s) => s.slug),
@@ -69,6 +78,7 @@ export default async function PlaybookDetailPage({
69
78
  relatedSections={relatedSections}
70
79
  adoption={adoption}
71
80
  allSlugs={allSlugs}
81
+ bookChapterLink={bookChapterLink}
72
82
  />
73
83
  </PageShell>
74
84
  );
@@ -9,7 +9,7 @@ import { PageShell } from "@/components/shared/page-shell";
9
9
  export const dynamic = "force-dynamic";
10
10
 
11
11
  export const metadata = {
12
- title: "Playbook | Stagent",
12
+ title: "User Guide | Stagent",
13
13
  };
14
14
 
15
15
  export default async function PlaybookPage() {
@@ -40,7 +40,7 @@ export default async function PlaybookPage() {
40
40
  new Date(lastGenerated) > new Date(lastVisit);
41
41
 
42
42
  return (
43
- <PageShell title="Playbook">
43
+ <PageShell title="User Guide">
44
44
  <PlaybookHomepage
45
45
  manifest={manifest}
46
46
  stage={stage}
@@ -1,9 +1,12 @@
1
1
  import { notFound } from "next/navigation";
2
+ import Link from "next/link";
2
3
  import { db } from "@/lib/db";
3
- import { workflows } from "@/lib/db/schema";
4
+ import { workflows, projects } from "@/lib/db/schema";
4
5
  import { eq } from "drizzle-orm";
5
6
  import { PageShell } from "@/components/shared/page-shell";
6
7
  import { WorkflowStatusView } from "@/components/workflows/workflow-status-view";
8
+ import { Badge } from "@/components/ui/badge";
9
+ import { FolderKanban } from "lucide-react";
7
10
 
8
11
  export const dynamic = "force-dynamic";
9
12
 
@@ -21,8 +24,31 @@ export default async function WorkflowDetailPage({
21
24
 
22
25
  if (!workflow) notFound();
23
26
 
27
+ // Fetch project name for back-link
28
+ let projectName: string | null = null;
29
+ if (workflow.projectId) {
30
+ const [project] = await db
31
+ .select({ name: projects.name })
32
+ .from(projects)
33
+ .where(eq(projects.id, workflow.projectId));
34
+ projectName = project?.name ?? null;
35
+ }
36
+
24
37
  return (
25
- <PageShell backHref="/workflows" backLabel="Back to Workflows">
38
+ <PageShell
39
+ backHref="/workflows"
40
+ backLabel="Back to Workflows"
41
+ actions={
42
+ workflow.projectId && projectName ? (
43
+ <Link href={`/projects/${workflow.projectId}`}>
44
+ <Badge variant="outline" className="gap-1.5 hover:bg-accent">
45
+ <FolderKanban className="h-3 w-3" />
46
+ {projectName}
47
+ </Badge>
48
+ </Link>
49
+ ) : undefined
50
+ }
51
+ >
26
52
  <WorkflowStatusView workflowId={id} />
27
53
  </PageShell>
28
54
  );