flowbook 0.2.5 → 0.2.7

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.
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "flowbook",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "type": "module",
5
5
  "bin": {
6
- "flowbook": "./dist/cli.js"
6
+ "flowbook": "dist/cli.js"
7
7
  },
8
8
  "files": [
9
9
  "dist",
@@ -2,12 +2,11 @@ import { useState, useMemo } from "react";
2
2
  import data from "virtual:flowbook-data";
3
3
  import { Sidebar } from "./components/Sidebar";
4
4
  import { FlowView } from "./components/FlowView";
5
- import { Header } from "./components/Header";
6
5
  import { EmptyState } from "./components/EmptyState";
7
6
 
8
7
  export function App() {
9
8
  const [selectedId, setSelectedId] = useState<string | null>(null);
10
- const [searchQuery, setSearchQuery] = useState("");
9
+ const [searchQuery] = useState("");
11
10
 
12
11
  const filteredFlows = useMemo(() => {
13
12
  if (!searchQuery) return data.flows;
@@ -27,25 +26,35 @@ export function App() {
27
26
  );
28
27
 
29
28
  return (
30
- <div className="h-screen flex flex-col bg-zinc-950 text-zinc-100">
31
- <Header
32
- searchQuery={searchQuery}
33
- onSearchChange={setSearchQuery}
34
- flowCount={data.flows.length}
35
- />
36
- <div className="flex flex-1 overflow-hidden">
37
- <Sidebar
38
- flows={filteredFlows}
39
- selectedId={selectedId}
40
- onSelect={setSelectedId}
41
- />
42
- <main className="flex-1 overflow-auto">
43
- {selectedFlow ? (
44
- <FlowView flow={selectedFlow} />
45
- ) : (
46
- <EmptyState flowCount={data.flows.length} />
47
- )}
48
- </main>
29
+ <div className="h-screen flex items-center justify-center p-6" style={{ background: 'var(--fb-bg-outer)' }}>
30
+ <div className="w-full max-w-6xl rounded-xl overflow-hidden shadow-2xl" style={{ background: 'var(--fb-bg-window)', border: '1px solid var(--fb-border)' }}>
31
+ {/* macOS-style title bar */}
32
+ <div className="flex items-center px-4 py-3 border-b" style={{ background: 'var(--fb-bg-titlebar)', borderColor: 'var(--fb-border)' }}>
33
+ <div className="flex items-center gap-2">
34
+ <div className="w-3 h-3 rounded-full bg-[#FF5F57]" />
35
+ <div className="w-3 h-3 rounded-full bg-[#FFBD2E]" />
36
+ <div className="w-3 h-3 rounded-full bg-[#27C93F]" />
37
+ </div>
38
+ <span className="ml-4 text-sm font-mono" style={{ color: 'var(--fb-text-muted)' }}>
39
+ flowbook — localhost:6200
40
+ </span>
41
+ </div>
42
+
43
+ {/* Content area */}
44
+ <div className="flex" style={{ height: '520px' }}>
45
+ <Sidebar
46
+ flows={filteredFlows}
47
+ selectedId={selectedId}
48
+ onSelect={setSelectedId}
49
+ />
50
+ <main className="flex-1 overflow-auto" style={{ background: 'var(--fb-bg-content)' }}>
51
+ {selectedFlow ? (
52
+ <FlowView flow={selectedFlow} />
53
+ ) : (
54
+ <EmptyState flowCount={data.flows.length} />
55
+ )}
56
+ </main>
57
+ </div>
49
58
  </div>
50
59
  </div>
51
60
  );
@@ -7,7 +7,8 @@ export function EmptyState({ flowCount }: EmptyStateProps) {
7
7
  <div className="flex items-center justify-center h-full">
8
8
  <div className="text-center max-w-lg px-4">
9
9
  <svg
10
- className="w-16 h-16 text-zinc-800 mx-auto mb-6"
10
+ className="w-16 h-16 mx-auto mb-6"
11
+ style={{ color: '#1e293b' }}
11
12
  viewBox="0 0 24 24"
12
13
  fill="none"
13
14
  stroke="currentColor"
@@ -21,20 +22,26 @@ export function EmptyState({ flowCount }: EmptyStateProps) {
21
22
  <path d="M6.5 10v1.5a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1V10" />
22
23
  <path d="M11.5 12.5V14" />
23
24
  </svg>
24
- <h2 className="text-xl font-semibold text-zinc-400 mb-2">
25
+ <h2 className="text-xl font-semibold mb-2" style={{ color: 'var(--fb-text-secondary)' }}>
25
26
  {flowCount > 0 ? "Select a flow" : "No flows found"}
26
27
  </h2>
27
- <p className="text-zinc-600 text-sm leading-relaxed">
28
+ <p className="text-sm leading-relaxed" style={{ color: 'var(--fb-text-muted)' }}>
28
29
  {flowCount > 0
29
30
  ? "Choose a flow from the sidebar to view its diagram."
30
31
  : "Create .flow.md or .flowchart.md files with mermaid diagrams to get started."}
31
32
  </p>
32
33
  {flowCount === 0 && (
33
- <div className="mt-8 bg-zinc-900 border border-zinc-800 rounded-xl p-5 text-left">
34
- <p className="text-xs text-zinc-500 mb-3 font-medium">
35
- Example: <code>auth/login.flow.md</code>
34
+ <div
35
+ className="mt-8 rounded-xl p-5 text-left border"
36
+ style={{
37
+ background: 'rgba(255,255,255,0.03)',
38
+ borderColor: 'var(--fb-border)',
39
+ }}
40
+ >
41
+ <p className="text-xs mb-3 font-medium" style={{ color: 'var(--fb-text-muted)' }}>
42
+ Example: <code style={{ color: 'var(--fb-accent-violet)' }}>auth/login.flow.md</code>
36
43
  </p>
37
- <pre className="text-xs text-zinc-400 font-mono leading-relaxed whitespace-pre overflow-x-auto">
44
+ <pre className="text-xs font-mono leading-relaxed whitespace-pre overflow-x-auto" style={{ color: 'var(--fb-text-secondary)' }}>
38
45
  {`---
39
46
  title: Login Flow
40
47
  category: Authentication
@@ -8,50 +8,30 @@ interface FlowViewProps {
8
8
  export function FlowView({ flow }: FlowViewProps) {
9
9
  return (
10
10
  <div className="p-8 max-w-5xl mx-auto">
11
- {/* Header */}
12
- <div className="mb-8">
13
- <div className="flex items-center gap-2 mb-3 flex-wrap">
14
- <span className="text-xs font-medium text-violet-300 bg-violet-500/10 border border-violet-500/20 px-2.5 py-0.5 rounded-full">
15
- {flow.category}
16
- </span>
17
- {flow.tags.map((tag) => (
18
- <span
19
- key={tag}
20
- className="text-xs text-zinc-400 bg-zinc-800/80 px-2 py-0.5 rounded-full"
21
- >
22
- {tag}
23
- </span>
24
- ))}
25
- </div>
26
- <h2 className="text-2xl font-bold text-zinc-50 tracking-tight">
27
- {flow.title}
28
- </h2>
29
- {flow.description && (
30
- <p className="mt-2 text-zinc-400 leading-relaxed">
31
- {flow.description}
32
- </p>
33
- )}
34
- <p className="mt-2 text-xs text-zinc-600 font-mono">{flow.filePath}</p>
35
- </div>
36
-
37
11
  {/* Diagrams */}
38
12
  <div className="space-y-6">
39
13
  {flow.mermaidBlocks.map((block, i) => (
40
14
  <div
41
15
  key={i}
42
- className="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 backdrop-blur-sm"
16
+ className="flex items-center justify-center min-h-[400px]"
43
17
  >
44
18
  <MermaidRenderer code={block} />
45
19
  </div>
46
20
  ))}
47
21
 
48
22
  {flow.mermaidBlocks.length === 0 && (
49
- <div className="bg-zinc-900/50 border border-zinc-800 rounded-xl p-12 text-center">
50
- <p className="text-zinc-500">
23
+ <div
24
+ className="rounded-xl p-12 text-center border"
25
+ style={{
26
+ background: 'rgba(255,255,255,0.03)',
27
+ borderColor: 'var(--fb-border)',
28
+ }}
29
+ >
30
+ <p style={{ color: 'var(--fb-text-muted)' }}>
51
31
  No mermaid diagrams found in this file.
52
32
  </p>
53
- <p className="text-zinc-600 text-sm mt-1">
54
- Add a <code className="text-violet-400">```mermaid</code> code
33
+ <p className="text-sm mt-1" style={{ color: 'var(--fb-text-muted)' }}>
34
+ Add a <code style={{ color: 'var(--fb-accent-violet)' }}>```mermaid</code> code
55
35
  block to see it rendered here.
56
36
  </p>
57
37
  </div>
@@ -9,23 +9,27 @@ function ensureInit() {
9
9
  startOnLoad: false,
10
10
  theme: "dark",
11
11
  securityLevel: "loose",
12
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, monospace",
12
+ fontFamily: "'Inter', ui-sans-serif, system-ui, sans-serif",
13
13
  themeVariables: {
14
14
  darkMode: true,
15
- background: "#18181b",
16
- mainBkg: "#27272a",
17
- primaryColor: "#7c3aed",
18
- primaryTextColor: "#f4f4f5",
19
- primaryBorderColor: "#6d28d9",
20
- secondaryColor: "#3f3f46",
21
- secondaryTextColor: "#d4d4d8",
22
- secondaryBorderColor: "#52525b",
23
- tertiaryColor: "#27272a",
24
- tertiaryTextColor: "#a1a1aa",
25
- tertiaryBorderColor: "#3f3f46",
26
- lineColor: "#71717a",
27
- textColor: "#e4e4e7",
28
- nodeTextColor: "#f4f4f5",
15
+ background: "#131a2b",
16
+ mainBkg: "#1e2740",
17
+ primaryColor: "#6366f1",
18
+ primaryTextColor: "#e2e8f0",
19
+ primaryBorderColor: "#4f46e5",
20
+ secondaryColor: "#1e293b",
21
+ secondaryTextColor: "#cbd5e1",
22
+ secondaryBorderColor: "#334155",
23
+ tertiaryColor: "#1e2740",
24
+ tertiaryTextColor: "#94a3b8",
25
+ tertiaryBorderColor: "#334155",
26
+ lineColor: "#6366f1",
27
+ textColor: "#e2e8f0",
28
+ nodeTextColor: "#e2e8f0",
29
+ nodeBorder: "#4f46e5",
30
+ clusterBkg: "#0f172a",
31
+ clusterBorder: "#1e293b",
32
+ edgeLabelBackground: "#131a2b",
29
33
  },
30
34
  });
31
35
  initialized = true;
@@ -71,13 +75,17 @@ export function MermaidRenderer({ code, className }: Props) {
71
75
  if (error) {
72
76
  return (
73
77
  <div
74
- className={`bg-red-500/10 border border-red-500/20 rounded-lg p-4 ${className ?? ""}`}
78
+ className={`rounded-lg p-4 border ${className ?? ""}`}
79
+ style={{
80
+ background: 'rgba(239, 68, 68, 0.08)',
81
+ borderColor: 'rgba(239, 68, 68, 0.2)',
82
+ }}
75
83
  >
76
- <p className="text-red-400 text-sm font-medium mb-2">
84
+ <p className="text-sm font-medium mb-2" style={{ color: '#f87171' }}>
77
85
  Render error
78
86
  </p>
79
- <p className="text-red-300/70 text-xs font-mono mb-3">{error}</p>
80
- <pre className="text-xs text-zinc-500 overflow-x-auto whitespace-pre-wrap">
87
+ <p className="text-xs font-mono mb-3" style={{ color: 'rgba(248, 113, 113, 0.7)' }}>{error}</p>
88
+ <pre className="text-xs overflow-x-auto whitespace-pre-wrap" style={{ color: 'var(--fb-text-muted)' }}>
81
89
  {code}
82
90
  </pre>
83
91
  </div>
@@ -89,7 +97,7 @@ export function MermaidRenderer({ code, className }: Props) {
89
97
  <div
90
98
  className={`flex items-center justify-center py-12 ${className ?? ""}`}
91
99
  >
92
- <div className="w-5 h-5 border-2 border-violet-500/30 border-t-violet-500 rounded-full animate-spin" />
100
+ <div className="w-5 h-5 border-2 rounded-full animate-spin" style={{ borderColor: 'rgba(99, 102, 241, 0.3)', borderTopColor: '#6366f1' }} />
93
101
  </div>
94
102
  );
95
103
  }
@@ -1,4 +1,4 @@
1
- import { useMemo, useState } from "react";
1
+ import { useMemo } from "react";
2
2
  import type { FlowEntry } from "../../types";
3
3
 
4
4
  interface SidebarProps {
@@ -8,10 +8,6 @@ interface SidebarProps {
8
8
  }
9
9
 
10
10
  export function Sidebar({ flows, selectedId, onSelect }: SidebarProps) {
11
- const [collapsedCategories, setCollapsedCategories] = useState<Set<string>>(
12
- new Set(),
13
- );
14
-
15
11
  const categories = useMemo(() => {
16
12
  const map = new Map<string, FlowEntry[]>();
17
13
  for (const flow of flows) {
@@ -22,67 +18,59 @@ export function Sidebar({ flows, selectedId, onSelect }: SidebarProps) {
22
18
  return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b));
23
19
  }, [flows]);
24
20
 
25
- function toggleCategory(cat: string) {
26
- setCollapsedCategories((prev) => {
27
- const next = new Set(prev);
28
- if (next.has(cat)) next.delete(cat);
29
- else next.add(cat);
30
- return next;
31
- });
21
+ const selectedFlow = flows.find((f) => f.id === selectedId);
22
+ const selectedCategory = selectedFlow?.category ?? null;
23
+
24
+ function handleCategoryClick(items: FlowEntry[]) {
25
+ if (items.length > 0) {
26
+ onSelect(items[0].id);
27
+ }
32
28
  }
33
29
 
34
30
  return (
35
- <aside className="w-64 border-r border-zinc-800 overflow-y-auto shrink-0 bg-zinc-950/50">
36
- <nav className="p-3 space-y-1">
37
- {categories.map(([category, items]) => {
38
- const isCollapsed = collapsedCategories.has(category);
31
+ <aside
32
+ className="w-64 overflow-y-auto shrink-0 border-r"
33
+ style={{
34
+ background: 'var(--fb-bg-sidebar)',
35
+ borderColor: 'var(--fb-border)',
36
+ }}
37
+ >
38
+ <div className="p-5">
39
+ <h2
40
+ className="text-xs font-bold uppercase tracking-widest mb-5"
41
+ style={{ color: 'var(--fb-text-primary)' }}
42
+ >
43
+ Categories
44
+ </h2>
45
+ <nav className="space-y-1">
46
+ {categories.map(([category, items]) => {
47
+ const isActive = selectedCategory === category;
39
48
 
40
- return (
41
- <div key={category}>
49
+ return (
42
50
  <button
43
- onClick={() => toggleCategory(category)}
44
- className="w-full flex items-center gap-1.5 px-2 py-1.5 text-xs font-semibold text-zinc-400 uppercase tracking-wider hover:text-zinc-300 transition-colors"
51
+ key={category}
52
+ onClick={() => handleCategoryClick(items)}
53
+ className={`w-full text-left px-4 py-2.5 rounded-lg text-sm transition-colors ${
54
+ isActive
55
+ ? "font-medium"
56
+ : "hover:bg-white/5"
57
+ }`}
58
+ style={{
59
+ color: isActive ? 'var(--fb-accent-green)' : 'var(--fb-text-secondary)',
60
+ background: isActive ? 'rgba(74, 222, 128, 0.08)' : undefined,
61
+ }}
45
62
  >
46
- <svg
47
- className={`w-3 h-3 transition-transform ${isCollapsed ? "" : "rotate-90"}`}
48
- viewBox="0 0 24 24"
49
- fill="currentColor"
50
- >
51
- <path d="M8 5l8 7-8 7z" />
52
- </svg>
53
63
  {category}
54
- <span className="ml-auto text-zinc-600 font-normal normal-case">
55
- {items.length}
56
- </span>
57
64
  </button>
58
- {!isCollapsed && (
59
- <ul className="ml-2 space-y-0.5">
60
- {items.map((flow) => (
61
- <li key={flow.id}>
62
- <button
63
- onClick={() => onSelect(flow.id)}
64
- className={`w-full text-left px-3 py-1.5 rounded-md text-sm transition-colors truncate ${
65
- selectedId === flow.id
66
- ? "bg-violet-500/15 text-violet-300 font-medium"
67
- : "text-zinc-400 hover:bg-zinc-800/60 hover:text-zinc-200"
68
- }`}
69
- title={flow.title}
70
- >
71
- {flow.title}
72
- </button>
73
- </li>
74
- ))}
75
- </ul>
76
- )}
77
- </div>
78
- );
79
- })}
80
- {categories.length === 0 && (
81
- <p className="text-sm text-zinc-600 px-2 py-4 text-center">
82
- No flows match your search
83
- </p>
84
- )}
85
- </nav>
65
+ );
66
+ })}
67
+ {categories.length === 0 && (
68
+ <p className="text-sm px-2 py-4 text-center" style={{ color: 'var(--fb-text-muted)' }}>
69
+ No flows found
70
+ </p>
71
+ )}
72
+ </nav>
73
+ </div>
86
74
  </aside>
87
75
  );
88
76
  }
Binary file
Binary file
Binary file
@@ -2,15 +2,27 @@
2
2
 
3
3
  :root {
4
4
  color-scheme: dark;
5
+ --fb-bg-outer: #0b0e17;
6
+ --fb-bg-window: #111827;
7
+ --fb-bg-titlebar: #161b28;
8
+ --fb-bg-content: #131a2b;
9
+ --fb-bg-sidebar: #111827;
10
+ --fb-border: #1e293b;
11
+ --fb-text-primary: #e2e8f0;
12
+ --fb-text-secondary: #94a3b8;
13
+ --fb-text-muted: #64748b;
14
+ --fb-accent-green: #4ade80;
15
+ --fb-accent-violet: #8b5cf6;
5
16
  }
6
17
 
7
18
  body {
8
19
  margin: 0;
9
20
  font-family:
10
- -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
11
- Arial, sans-serif;
21
+ "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
22
+ "Helvetica Neue", Arial, sans-serif;
12
23
  -webkit-font-smoothing: antialiased;
13
24
  -moz-osx-font-smoothing: grayscale;
25
+ background: var(--fb-bg-outer);
14
26
  }
15
27
 
16
28
  /* Mermaid SVG sizing */
@@ -30,10 +42,10 @@ body {
30
42
  }
31
43
 
32
44
  ::-webkit-scrollbar-thumb {
33
- background: #3f3f46;
45
+ background: #1e293b;
34
46
  border-radius: 3px;
35
47
  }
36
48
 
37
49
  ::-webkit-scrollbar-thumb:hover {
38
- background: #52525b;
50
+ background: #334155;
39
51
  }