coaia-visualizer 1.5.10 → 1.6.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.
- package/README.md +5 -0
- package/components/chart-detail-editable.tsx +2 -0
- package/components/chart-detail.tsx +6 -1
- package/components/edit-action-step.tsx +2 -0
- package/components/github-provenance.tsx +226 -0
- package/components/relation-graph.tsx +91 -27
- package/index.tsx +26 -0
- package/lib/github-provenance.ts +316 -0
- package/lib/types.ts +15 -2
- package/next-env.d.ts +6 -0
- package/package.json +27 -1
- package/.coaia/jgwill-coaia-visualizer.jsonl +0 -6
- package/.dockerignore +0 -9
- package/COMPLETE_IMPLEMENTATION_REPORT.md +0 -215
- package/QUICK_START_MCP_TESTING.md +0 -236
- package/README_TELESCOPED_NAVIGATION.md +0 -247
- package/STC.md +0 -0
- package/STCGOAL.md +0 -0
- package/STCISSUE.md +0 -0
- package/STCKIN.md +0 -0
- package/STCMASTERY.md +0 -0
- package/STUDY_REPORT.md +0 -510
- package/components.json +0 -21
- package/direct-test.sh +0 -180
- package/docker-compose.test.yml +0 -69
- package/test-data/test-master.jsonl +0 -11
- package/test-run.log +0 -101
- package/test-scripts/README.md +0 -325
- package/test-scripts/run-all-tests.sh +0 -38
- package/test-scripts/test-01-basic-operations.sh +0 -87
- package/test-scripts/test-02-telescope-creation.sh +0 -91
- package/test-scripts/test-03-navigation.sh +0 -87
- package/test-scripts/test-global-cli.spec.ts +0 -220
- package/test-scripts/verify-global-cli.sh +0 -87
- package/tsconfig.json +0 -41
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import type { EntityRecord, RelationRecord } from "./types"
|
|
2
|
+
|
|
3
|
+
export const GITHUB_SYNC_STATES = ["synced", "diverged", "conflict", "project-only", "chart-only"] as const
|
|
4
|
+
|
|
5
|
+
export type GithubSyncState = (typeof GITHUB_SYNC_STATES)[number]
|
|
6
|
+
|
|
7
|
+
export interface GithubIssueRef {
|
|
8
|
+
owner: string
|
|
9
|
+
repo: string
|
|
10
|
+
number: number
|
|
11
|
+
nodeId?: string
|
|
12
|
+
url?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface GithubProjectItemRef {
|
|
16
|
+
projectOwner?: string
|
|
17
|
+
projectNumber?: number
|
|
18
|
+
projectTitle?: string
|
|
19
|
+
itemId?: string
|
|
20
|
+
projectId?: string
|
|
21
|
+
url?: string
|
|
22
|
+
fields?: Record<string, unknown>
|
|
23
|
+
fieldValues?: Record<string, unknown>
|
|
24
|
+
projectFields?: Record<string, unknown>
|
|
25
|
+
[key: string]: unknown
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface GithubSourceProvenance {
|
|
29
|
+
system?: string
|
|
30
|
+
toolName?: string
|
|
31
|
+
sessionId?: string
|
|
32
|
+
createdAt?: string
|
|
33
|
+
version?: string
|
|
34
|
+
[key: string]: unknown
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface GithubEntityProvenance {
|
|
38
|
+
issue?: GithubIssueRef
|
|
39
|
+
projectItems: GithubProjectItemRef[]
|
|
40
|
+
syncState?: GithubSyncState
|
|
41
|
+
lastSyncedAt?: string
|
|
42
|
+
source?: GithubSourceProvenance
|
|
43
|
+
hasGithubMetadata: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface GithubProjectFieldEntry {
|
|
47
|
+
key: string
|
|
48
|
+
label: string
|
|
49
|
+
value: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const GITHUB_PROJECT_FIELD_KEYS = [
|
|
53
|
+
"goal",
|
|
54
|
+
"current_reality",
|
|
55
|
+
"observations",
|
|
56
|
+
"question",
|
|
57
|
+
"Status",
|
|
58
|
+
"phase",
|
|
59
|
+
"session_id",
|
|
60
|
+
"four_dir_east",
|
|
61
|
+
"four_dir_south",
|
|
62
|
+
"four_dir_west",
|
|
63
|
+
"four_dir_north",
|
|
64
|
+
"relational_assessed",
|
|
65
|
+
"relational_principles",
|
|
66
|
+
] as const
|
|
67
|
+
|
|
68
|
+
export const GITHUB_BRIDGE_RELATION_TYPES = ["synced_to_github", "linked_to_issue", "project_lens_of"] as const
|
|
69
|
+
|
|
70
|
+
export type GithubBridgeRelationType = (typeof GITHUB_BRIDGE_RELATION_TYPES)[number]
|
|
71
|
+
|
|
72
|
+
const PROJECT_FIELD_LABELS: Record<string, string> = {
|
|
73
|
+
goal: "goal",
|
|
74
|
+
current_reality: "current reality",
|
|
75
|
+
observations: "observations",
|
|
76
|
+
question: "question",
|
|
77
|
+
Status: "Status",
|
|
78
|
+
phase: "phase",
|
|
79
|
+
session_id: "session",
|
|
80
|
+
four_dir_east: "East",
|
|
81
|
+
four_dir_south: "South",
|
|
82
|
+
four_dir_west: "West",
|
|
83
|
+
four_dir_north: "North",
|
|
84
|
+
relational_assessed: "relational assessed",
|
|
85
|
+
relational_principles: "relational principles",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
89
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function firstString(...values: unknown[]): string | undefined {
|
|
93
|
+
for (const value of values) {
|
|
94
|
+
if (typeof value === "string" && value.trim()) {
|
|
95
|
+
return value.trim()
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function firstNumber(...values: unknown[]): number | undefined {
|
|
101
|
+
for (const value of values) {
|
|
102
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
103
|
+
return value
|
|
104
|
+
}
|
|
105
|
+
if (typeof value === "string" && value.trim()) {
|
|
106
|
+
const parsed = Number(value)
|
|
107
|
+
if (Number.isFinite(parsed)) {
|
|
108
|
+
return parsed
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizeIssueCandidate(candidate: unknown): GithubIssueRef | undefined {
|
|
115
|
+
if (!isRecord(candidate)) return undefined
|
|
116
|
+
|
|
117
|
+
const owner = firstString(candidate.owner, candidate.ownerLogin, candidate.repositoryOwner)
|
|
118
|
+
const repo = firstString(candidate.repo, candidate.repository, candidate.repositoryName)
|
|
119
|
+
const number = firstNumber(candidate.number, candidate.issue_number, candidate.issueNumber, candidate.issue)
|
|
120
|
+
|
|
121
|
+
if (!owner || !repo || !number) return undefined
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
owner,
|
|
125
|
+
repo,
|
|
126
|
+
number,
|
|
127
|
+
nodeId: firstString(candidate.nodeId, candidate.node_id),
|
|
128
|
+
url: firstString(candidate.url, candidate.html_url) ?? `https://github.com/${owner}/${repo}/issues/${number}`,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeProjectItemCandidate(candidate: unknown, fallbackOwner?: string): GithubProjectItemRef | undefined {
|
|
133
|
+
if (!isRecord(candidate)) return undefined
|
|
134
|
+
|
|
135
|
+
const projectOwner = firstString(candidate.projectOwner, candidate.owner, candidate.ownerLogin, fallbackOwner)
|
|
136
|
+
const projectNumber = firstNumber(candidate.projectNumber, candidate.project_number, candidate.number)
|
|
137
|
+
const itemId = firstString(candidate.itemId, candidate.item_id, candidate.projectItemId, candidate.project_id)
|
|
138
|
+
const projectId = firstString(candidate.projectId, candidate.project_id)
|
|
139
|
+
const projectTitle = firstString(candidate.projectTitle, candidate.title, candidate.project)
|
|
140
|
+
const url = firstString(candidate.url)
|
|
141
|
+
|
|
142
|
+
if (!projectOwner && !projectNumber && !itemId && !projectId && !projectTitle && !url) {
|
|
143
|
+
return undefined
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
...candidate,
|
|
148
|
+
projectOwner,
|
|
149
|
+
projectNumber,
|
|
150
|
+
projectTitle,
|
|
151
|
+
itemId,
|
|
152
|
+
projectId,
|
|
153
|
+
url,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function getGithubIssueRef(entity?: EntityRecord): GithubIssueRef | undefined {
|
|
158
|
+
if (!entity) return undefined
|
|
159
|
+
|
|
160
|
+
const metadata = entity.metadata ?? {}
|
|
161
|
+
const github = isRecord(metadata.github) ? metadata.github : undefined
|
|
162
|
+
const syncTarget = isRecord(metadata.sync_target) ? metadata.sync_target : undefined
|
|
163
|
+
const githubRef = isRecord(metadata.github_ref) ? metadata.github_ref : undefined
|
|
164
|
+
|
|
165
|
+
return normalizeIssueCandidate(github?.issue) ?? normalizeIssueCandidate(syncTarget) ?? normalizeIssueCandidate(githubRef)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getGithubProjectItems(entity?: EntityRecord): GithubProjectItemRef[] {
|
|
169
|
+
if (!entity) return []
|
|
170
|
+
|
|
171
|
+
const metadata = entity.metadata ?? {}
|
|
172
|
+
const github = isRecord(metadata.github) ? metadata.github : undefined
|
|
173
|
+
const syncTarget = isRecord(metadata.sync_target) ? metadata.sync_target : undefined
|
|
174
|
+
const fallbackOwner = getGithubIssueRef(entity)?.owner
|
|
175
|
+
|
|
176
|
+
if (Array.isArray(github?.projectItems)) {
|
|
177
|
+
return github.projectItems
|
|
178
|
+
.map((item) => normalizeProjectItemCandidate(item, fallbackOwner))
|
|
179
|
+
.filter((item): item is GithubProjectItemRef => Boolean(item))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const canonicalProjectItem = normalizeProjectItemCandidate(github?.projectItem, fallbackOwner)
|
|
183
|
+
if (canonicalProjectItem) {
|
|
184
|
+
return [canonicalProjectItem]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const legacyProjectItem = normalizeProjectItemCandidate(syncTarget, fallbackOwner)
|
|
188
|
+
return legacyProjectItem ? [legacyProjectItem] : []
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function getGithubSyncState(entityOrMetadata?: EntityRecord | Record<string, unknown>): GithubSyncState | undefined {
|
|
192
|
+
const metadata =
|
|
193
|
+
entityOrMetadata && "metadata" in entityOrMetadata && isRecord(entityOrMetadata.metadata)
|
|
194
|
+
? entityOrMetadata.metadata
|
|
195
|
+
: entityOrMetadata
|
|
196
|
+
|
|
197
|
+
if (!isRecord(metadata)) return undefined
|
|
198
|
+
|
|
199
|
+
const github = isRecord(metadata.github) ? metadata.github : undefined
|
|
200
|
+
const syncState = github?.syncState ?? metadata.syncState
|
|
201
|
+
|
|
202
|
+
return typeof syncState === "string" && GITHUB_SYNC_STATES.includes(syncState as GithubSyncState)
|
|
203
|
+
? (syncState as GithubSyncState)
|
|
204
|
+
: undefined
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function getGithubSource(entity?: EntityRecord): GithubSourceProvenance | undefined {
|
|
208
|
+
if (!entity) return undefined
|
|
209
|
+
|
|
210
|
+
const source = entity.metadata?.source
|
|
211
|
+
return isRecord(source) ? (source as GithubSourceProvenance) : undefined
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function getGithubEntityProvenance(entity?: EntityRecord): GithubEntityProvenance {
|
|
215
|
+
const metadata = entity?.metadata ?? {}
|
|
216
|
+
const github = isRecord(metadata.github) ? metadata.github : undefined
|
|
217
|
+
const issue = getGithubIssueRef(entity)
|
|
218
|
+
const projectItems = getGithubProjectItems(entity)
|
|
219
|
+
const syncTarget = isRecord(metadata.sync_target) ? metadata.sync_target : undefined
|
|
220
|
+
const githubRef = isRecord(metadata.github_ref) ? metadata.github_ref : undefined
|
|
221
|
+
const source = getGithubSource(entity)
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
issue,
|
|
225
|
+
projectItems,
|
|
226
|
+
syncState: getGithubSyncState(entity),
|
|
227
|
+
lastSyncedAt: firstString(github?.lastSyncedAt, metadata.lastSyncedAt),
|
|
228
|
+
source,
|
|
229
|
+
hasGithubMetadata:
|
|
230
|
+
Boolean(github) ||
|
|
231
|
+
Boolean(syncTarget) ||
|
|
232
|
+
Boolean(githubRef) ||
|
|
233
|
+
source?.system === "coaia-github" ||
|
|
234
|
+
Boolean(issue) ||
|
|
235
|
+
projectItems.length > 0,
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function formatGithubIssueRef(issue?: GithubIssueRef): string | undefined {
|
|
240
|
+
if (!issue) return undefined
|
|
241
|
+
return `${issue.owner}/${issue.repo}#${issue.number}`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function formatProjectItemRef(projectItem?: GithubProjectItemRef): string | undefined {
|
|
245
|
+
if (!projectItem) return undefined
|
|
246
|
+
|
|
247
|
+
if (projectItem.projectOwner && projectItem.projectNumber) {
|
|
248
|
+
return `${projectItem.projectOwner}/${projectItem.projectNumber}`
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return projectItem.projectTitle || projectItem.itemId || projectItem.projectId || projectItem.url
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function getProjectFieldEntries(
|
|
255
|
+
entity: EntityRecord | undefined,
|
|
256
|
+
projectItem?: GithubProjectItemRef,
|
|
257
|
+
): GithubProjectFieldEntry[] {
|
|
258
|
+
const metadata = entity?.metadata ?? {}
|
|
259
|
+
const github = isRecord(metadata.github) ? metadata.github : undefined
|
|
260
|
+
const sources = [
|
|
261
|
+
projectItem?.fields,
|
|
262
|
+
projectItem?.fieldValues,
|
|
263
|
+
projectItem?.projectFields,
|
|
264
|
+
github?.fields,
|
|
265
|
+
github?.fieldValues,
|
|
266
|
+
github?.projectFields,
|
|
267
|
+
metadata,
|
|
268
|
+
github,
|
|
269
|
+
].filter(isRecord)
|
|
270
|
+
|
|
271
|
+
return GITHUB_PROJECT_FIELD_KEYS.flatMap((key) => {
|
|
272
|
+
for (const source of sources) {
|
|
273
|
+
if (!(key in source)) continue
|
|
274
|
+
const value = stringifyFieldValue(source[key])
|
|
275
|
+
if (value) {
|
|
276
|
+
return [{ key, label: PROJECT_FIELD_LABELS[key], value }]
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return []
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function getGithubBridgeRelationType(relation: RelationRecord): GithubBridgeRelationType | undefined {
|
|
284
|
+
const relationType = relation.relationType
|
|
285
|
+
if (GITHUB_BRIDGE_RELATION_TYPES.includes(relationType as GithubBridgeRelationType)) {
|
|
286
|
+
return relationType as GithubBridgeRelationType
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const metadata = relation.metadata ?? {}
|
|
290
|
+
const github = isRecord(metadata.github) ? metadata.github : undefined
|
|
291
|
+
const context = firstString(
|
|
292
|
+
metadata.context,
|
|
293
|
+
metadata.relation,
|
|
294
|
+
metadata.relationName,
|
|
295
|
+
metadata.semanticType,
|
|
296
|
+
github?.context,
|
|
297
|
+
github?.relation,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return GITHUB_BRIDGE_RELATION_TYPES.includes(context as GithubBridgeRelationType)
|
|
301
|
+
? (context as GithubBridgeRelationType)
|
|
302
|
+
: undefined
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function stringifyFieldValue(value: unknown): string | undefined {
|
|
306
|
+
if (value === null || value === undefined) return undefined
|
|
307
|
+
if (typeof value === "string") return value.trim() || undefined
|
|
308
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value)
|
|
309
|
+
if (Array.isArray(value)) {
|
|
310
|
+
const rendered = value.map(stringifyFieldValue).filter(Boolean).join(", ")
|
|
311
|
+
return rendered || undefined
|
|
312
|
+
}
|
|
313
|
+
if (isRecord(value)) {
|
|
314
|
+
return firstString(value.name, value.text, value.value, value.title, value.label) ?? JSON.stringify(value)
|
|
315
|
+
}
|
|
316
|
+
}
|
package/lib/types.ts
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
export interface EntityRecord {
|
|
4
4
|
type: "entity"
|
|
5
5
|
name: string
|
|
6
|
-
entityType:
|
|
6
|
+
entityType:
|
|
7
|
+
| "structural_tension_chart"
|
|
8
|
+
| "desired_outcome"
|
|
9
|
+
| "current_reality"
|
|
10
|
+
| "action_step"
|
|
11
|
+
| "narrative_beat"
|
|
12
|
+
| "custom"
|
|
13
|
+
| (string & {})
|
|
7
14
|
observations: string[]
|
|
8
15
|
metadata: Record<string, any>
|
|
9
16
|
}
|
|
@@ -12,7 +19,13 @@ export interface RelationRecord {
|
|
|
12
19
|
type: "relation"
|
|
13
20
|
from: string
|
|
14
21
|
to: string
|
|
15
|
-
relationType:
|
|
22
|
+
relationType:
|
|
23
|
+
| "contains"
|
|
24
|
+
| "creates_tension_with"
|
|
25
|
+
| "advances_toward"
|
|
26
|
+
| "documents"
|
|
27
|
+
| "custom"
|
|
28
|
+
| (string & {})
|
|
16
29
|
metadata: Record<string, any>
|
|
17
30
|
}
|
|
18
31
|
|
package/next-env.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coaia-visualizer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.tsx",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
".": "./index.tsx",
|
|
10
10
|
"./lib/types": "./lib/types.ts",
|
|
11
11
|
"./lib/jsonl-parser": "./lib/jsonl-parser.ts",
|
|
12
|
+
"./lib/github-provenance": "./lib/github-provenance.ts",
|
|
12
13
|
"./lib/utils": "./lib/utils.ts",
|
|
13
14
|
"./hooks/*": "./hooks/*.ts",
|
|
14
15
|
"./components/*": "./components/*.tsx",
|
|
@@ -18,6 +19,30 @@
|
|
|
18
19
|
"bin": {
|
|
19
20
|
"coaia-visualizer": "dist/cli.js"
|
|
20
21
|
},
|
|
22
|
+
"files": [
|
|
23
|
+
"app/**/*",
|
|
24
|
+
"components/**/*",
|
|
25
|
+
"dist/**/*",
|
|
26
|
+
"hooks/**/*",
|
|
27
|
+
"lib/**/*",
|
|
28
|
+
"mcp/**/*",
|
|
29
|
+
"public/**/*",
|
|
30
|
+
"rispecs/**/*",
|
|
31
|
+
"styles/**/*",
|
|
32
|
+
"cli.ts",
|
|
33
|
+
"index.tsx",
|
|
34
|
+
"next-env.d.ts",
|
|
35
|
+
"next.config.mjs",
|
|
36
|
+
"postcss.config.mjs",
|
|
37
|
+
"Dockerfile",
|
|
38
|
+
"Dockerfile.app",
|
|
39
|
+
"docker-build-push.sh",
|
|
40
|
+
"docker-entrypoint.sh",
|
|
41
|
+
"mcp-config.json",
|
|
42
|
+
"README.md",
|
|
43
|
+
"KINSHIP.md",
|
|
44
|
+
"NAMING.md"
|
|
45
|
+
],
|
|
21
46
|
"scripts": {
|
|
22
47
|
"build": "next build",
|
|
23
48
|
"build:cli": "tsc cli.ts --outDir dist --module esnext --target es2022 --moduleResolution bundler --esModuleInterop --skipLibCheck",
|
|
@@ -27,6 +52,7 @@
|
|
|
27
52
|
"start": "next start",
|
|
28
53
|
"test": "playwright test",
|
|
29
54
|
"test:global-cli": "playwright test test-scripts/test-global-cli.spec.ts",
|
|
55
|
+
"verify:github-provenance": "node --experimental-strip-types test-scripts/verify-github-provenance.mjs",
|
|
30
56
|
"docker:build": "docker build -t jgwill/coaia:visualizer .",
|
|
31
57
|
"docker:push": "docker push jgwill/coaia:visualizer",
|
|
32
58
|
"docker:build-push": "npm run docker:build && npm run docker:push",
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
{"type":"entity","name":"chart_1770523106343_chart","entityType":"structural_tension_chart","observations":["Chart created for jgwill/coaia-visualizer on 2026-02-08T03:58:26.343Z"],"metadata":{"chartId":"chart_1770523106343","repository":"jgwill/coaia-visualizer","level":0,"createdAt":"2026-02-08T03:58:26.343Z","updatedAt":"2026-02-08T03:58:26.343Z"}}
|
|
2
|
-
{"type":"entity","name":"chart_1770523106343_desired_outcome","entityType":"desired_outcome","observations":["Successful development and collaboration on jgwill/coaia-visualizer"],"metadata":{"chartId":"chart_1770523106343","createdAt":"2026-02-08T03:58:26.343Z","updatedAt":"2026-02-08T03:58:26.343Z"}}
|
|
3
|
-
{"type":"entity","name":"chart_1770523106343_current_reality","entityType":"current_reality","observations":["Repository initialized with STC bot integration","[2026-02-08T03:58:26.404Z] @stcissue triggered: Issue #8 - LIVE_MODE_DESIGN.md (issues.opened)","[2026-02-08T04:38:12.548Z] @stcissue triggered: Issue #8 - Live Narrative Witness Mode (issues.edited)","[2026-02-12T04:05:37.474Z] @stcissue triggered: Issue #9 - file watching issue (issues.opened)","[2026-02-18T19:29:54.244Z] @stcissue triggered: Issue #10 - Containerization (issues.opened)"],"metadata":{"chartId":"chart_1770523106343","createdAt":"2026-02-08T03:58:26.343Z","updatedAt":"2026-02-18T19:29:54.244Z"}}
|
|
4
|
-
{"type":"relation","from":"chart_1770523106343_chart","to":"chart_1770523106343_desired_outcome","relationType":"contains","metadata":{"createdAt":"2026-02-08T03:58:26.343Z"}}
|
|
5
|
-
{"type":"relation","from":"chart_1770523106343_chart","to":"chart_1770523106343_current_reality","relationType":"contains","metadata":{"createdAt":"2026-02-08T03:58:26.343Z"}}
|
|
6
|
-
{"type":"relation","from":"chart_1770523106343_current_reality","to":"chart_1770523106343_desired_outcome","relationType":"creates_tension_with","metadata":{"createdAt":"2026-02-08T03:58:26.343Z"}}
|
package/.dockerignore
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
# ✅ Telescoped Chart Navigation - FINAL IMPLEMENTATION
|
|
2
|
-
|
|
3
|
-
## Summary
|
|
4
|
-
|
|
5
|
-
Successfully implemented **telescoped chart navigation with proper UI placement** in the action hover toolbar.
|
|
6
|
-
|
|
7
|
-
## The Correct Implementation
|
|
8
|
-
|
|
9
|
-
### What You See Now
|
|
10
|
-
|
|
11
|
-
When viewing a chart's **Action Steps** section:
|
|
12
|
-
|
|
13
|
-
1. **Hover over any action** to reveal the toolbar (right side)
|
|
14
|
-
2. **For telescoped charts**, you'll see these icons in the toolbar:
|
|
15
|
-
- 👁️ **Eye icon** (Telescope) - Opens the chart ← **THIS IS THE KEY**
|
|
16
|
-
- 📅 **Calendar icon** - Edit due date
|
|
17
|
-
- ✏️ **Pencil icon** - Edit description
|
|
18
|
-
- 🗑️ **Trash icon** - Delete
|
|
19
|
-
|
|
20
|
-
3. **Visual indicators**:
|
|
21
|
-
- "📊 Chart" badge next to action number
|
|
22
|
-
- Gradient background (subtle primary color)
|
|
23
|
-
- Colored border that brightens on hover
|
|
24
|
-
- Shadow appears on hover
|
|
25
|
-
|
|
26
|
-
### Action Toolbar Layout
|
|
27
|
-
|
|
28
|
-
\`\`\`
|
|
29
|
-
┌──────────────────────────────────────────────────────────┐
|
|
30
|
-
│ ○ Action 1 📊 Chart │
|
|
31
|
-
│ Fix chart-editor to create telescoped charts │
|
|
32
|
-
│ [👁] [📅] [✏️] [🗑️] ← Hover │
|
|
33
|
-
└──────────────────────────────────────────────────────────┘
|
|
34
|
-
↑
|
|
35
|
-
Telescope icon!
|
|
36
|
-
\`\`\`
|
|
37
|
-
|
|
38
|
-
## How To Use
|
|
39
|
-
|
|
40
|
-
### Step 1: Identify Telescoped Charts
|
|
41
|
-
Look for the **"📊 Chart"** badge next to the action number.
|
|
42
|
-
|
|
43
|
-
### Step 2: Hover Over the Action
|
|
44
|
-
Move your mouse over the action card to reveal the toolbar on the right.
|
|
45
|
-
|
|
46
|
-
### Step 3: Click the Eye Icon 👁️
|
|
47
|
-
The **Eye icon** (first in the toolbar) opens the telescoped chart.
|
|
48
|
-
|
|
49
|
-
### Step 4: Navigate the Chart
|
|
50
|
-
You're now viewing the action as a full structural tension chart:
|
|
51
|
-
- **"← Back to Parent Chart"** button at the top
|
|
52
|
-
- Full chart editor (desired outcome, current reality, actions)
|
|
53
|
-
- Can add sub-actions (creates Level 2 charts)
|
|
54
|
-
|
|
55
|
-
## Technical Implementation
|
|
56
|
-
|
|
57
|
-
### Files Modified
|
|
58
|
-
|
|
59
|
-
**components/edit-action-step.tsx**:
|
|
60
|
-
\`\`\`typescript
|
|
61
|
-
// Added Eye icon to hover toolbar (line ~97)
|
|
62
|
-
{isTelescopedChart && onNavigateToChart && (
|
|
63
|
-
<Button
|
|
64
|
-
variant="ghost"
|
|
65
|
-
size="sm"
|
|
66
|
-
className="h-7 px-2 text-primary hover:text-primary hover:bg-primary/10"
|
|
67
|
-
onClick={onNavigateToChart}
|
|
68
|
-
title="Open telescoped chart"
|
|
69
|
-
>
|
|
70
|
-
<Eye className="w-4 h-4" />
|
|
71
|
-
</Button>
|
|
72
|
-
)}
|
|
73
|
-
\`\`\`
|
|
74
|
-
|
|
75
|
-
**Placement**: First button in the hover toolbar, before Calendar/Pencil/Trash
|
|
76
|
-
|
|
77
|
-
**Visual Design**:
|
|
78
|
-
- Primary color text (stands out)
|
|
79
|
-
- Primary/10 background on hover
|
|
80
|
-
- Size: w-4 h-4 (slightly larger than other icons for prominence)
|
|
81
|
-
- Title tooltip: "Open telescoped chart"
|
|
82
|
-
|
|
83
|
-
### Navigation Stack
|
|
84
|
-
|
|
85
|
-
**app/page.tsx**:
|
|
86
|
-
- `chartNavigationStack: Chart[]` - Tracks navigation history
|
|
87
|
-
- `handleNavigateToSubChart()` - Pushes current, navigates to sub-chart
|
|
88
|
-
- `handleNavigateBack()` - Pops from stack, returns to parent
|
|
89
|
-
|
|
90
|
-
**components/chart-detail-editable.tsx**:
|
|
91
|
-
- Finds sub-chart matching the action
|
|
92
|
-
- Passes `onNavigateToChart` callback to EditActionStep
|
|
93
|
-
- Shows "← Back" button when viewing sub-charts
|
|
94
|
-
|
|
95
|
-
## Testing Results
|
|
96
|
-
|
|
97
|
-
✅ **Automated Tests** (Playwright):
|
|
98
|
-
- Found 2 Eye icons (telescope) in hover toolbar
|
|
99
|
-
- Successfully clicked Eye icon
|
|
100
|
-
- Navigated to sub-chart (back button appeared)
|
|
101
|
-
- Navigation works correctly
|
|
102
|
-
|
|
103
|
-
✅ **Build Status**:
|
|
104
|
-
- TypeScript: Zero errors
|
|
105
|
-
- Next.js: Production build successful
|
|
106
|
-
- Version: 1.4.0
|
|
107
|
-
|
|
108
|
-
✅ **Visual Verification**:
|
|
109
|
-
- Screenshots confirm Eye icon appears in toolbar
|
|
110
|
-
- Icon is properly styled (primary color)
|
|
111
|
-
- Hover state works correctly
|
|
112
|
-
- Navigation successful
|
|
113
|
-
|
|
114
|
-
## Design Specifications
|
|
115
|
-
|
|
116
|
-
### Icon Order in Toolbar
|
|
117
|
-
1. 👁️ **Eye** (Telescope) - Only for telescoped charts
|
|
118
|
-
2. 📅 **Calendar** - Due date
|
|
119
|
-
3. ✏️ **Pencil** - Edit description
|
|
120
|
-
4. 🗑️ **Trash** - Delete
|
|
121
|
-
|
|
122
|
-
### Color Coding
|
|
123
|
-
- **Eye icon**: `text-primary` (blue/theme color)
|
|
124
|
-
- **Eye hover**: `hover:bg-primary/10` (subtle highlight)
|
|
125
|
-
- Other icons: Default ghost button styling
|
|
126
|
-
|
|
127
|
-
### Size & Spacing
|
|
128
|
-
- **All toolbar icons**: `h-7 px-2` (consistent)
|
|
129
|
-
- **Eye icon size**: `w-4 h-4` (slightly larger for emphasis)
|
|
130
|
-
- **Toolbar gap**: `gap-1` (tight spacing)
|
|
131
|
-
- **Opacity**: `opacity-0 group-hover:opacity-100` (reveal on hover)
|
|
132
|
-
|
|
133
|
-
## User Experience
|
|
134
|
-
|
|
135
|
-
### Visual Hierarchy
|
|
136
|
-
1. **Badge first**: "📊 Chart" immediately identifies telescoped charts
|
|
137
|
-
2. **Gradient + border**: Card stands out visually
|
|
138
|
-
3. **Hover reveals tools**: Toolbar appears smoothly on hover
|
|
139
|
-
4. **Eye icon first**: Positioned first in toolbar for prominence
|
|
140
|
-
5. **Primary color**: Eye icon uses primary color to stand out
|
|
141
|
-
|
|
142
|
-
### Interaction Flow
|
|
143
|
-
\`\`\`
|
|
144
|
-
1. User sees action with "📊 Chart" badge
|
|
145
|
-
↓
|
|
146
|
-
2. User hovers over action
|
|
147
|
-
↓
|
|
148
|
-
3. Toolbar appears with Eye icon first
|
|
149
|
-
↓
|
|
150
|
-
4. User clicks Eye icon
|
|
151
|
-
↓
|
|
152
|
-
5. Navigate to full chart view
|
|
153
|
-
↓
|
|
154
|
-
6. User works in chart (edit, add actions)
|
|
155
|
-
↓
|
|
156
|
-
7. User clicks "← Back to Parent Chart"
|
|
157
|
-
↓
|
|
158
|
-
8. Return to previous level
|
|
159
|
-
\`\`\`
|
|
160
|
-
|
|
161
|
-
## Comparison: Before vs After
|
|
162
|
-
|
|
163
|
-
### Before (WRONG)
|
|
164
|
-
- ❌ "Open" button below text (out of sight)
|
|
165
|
-
- ❌ Not in the toolbar where users expect tools
|
|
166
|
-
- ❌ Easy to miss
|
|
167
|
-
|
|
168
|
-
### After (CORRECT)
|
|
169
|
-
- ✅ Eye icon in hover toolbar (with other tools)
|
|
170
|
-
- ✅ Consistent with Calendar/Pencil/Trash placement
|
|
171
|
-
- ✅ Primary color makes it stand out
|
|
172
|
-
- ✅ Tooltips explain functionality
|
|
173
|
-
- ✅ Positioned first in toolbar
|
|
174
|
-
|
|
175
|
-
## Accessibility
|
|
176
|
-
|
|
177
|
-
✅ **Keyboard Navigation**: Tab through actions, reach toolbar buttons
|
|
178
|
-
✅ **Focus Indicators**: Visible focus rings on all buttons
|
|
179
|
-
✅ **Tooltips**: "Open telescoped chart" on hover
|
|
180
|
-
✅ **Icons + Semantic HTML**: Proper button elements
|
|
181
|
-
✅ **Color Contrast**: Primary color meets WCAG standards
|
|
182
|
-
|
|
183
|
-
## Mobile Responsive
|
|
184
|
-
|
|
185
|
-
✅ **Touch Targets**: All toolbar buttons ≥44px touch target
|
|
186
|
-
✅ **Hover Alternative**: On mobile, toolbar always visible on telescoped charts
|
|
187
|
-
✅ **Icon Size**: Large enough for easy tapping
|
|
188
|
-
✅ **Spacing**: Adequate gap between toolbar buttons
|
|
189
|
-
|
|
190
|
-
## Known Issues
|
|
191
|
-
|
|
192
|
-
None. All functionality verified and working correctly.
|
|
193
|
-
|
|
194
|
-
## Future Enhancements
|
|
195
|
-
|
|
196
|
-
- [ ] Keyboard shortcut to open telescoped chart (e.g., Enter when focused)
|
|
197
|
-
- [ ] Breadcrumb trail showing full navigation path
|
|
198
|
-
- [ ] Visual tree view of chart hierarchy
|
|
199
|
-
- [ ] Quick preview on hover (tooltip with chart summary)
|
|
200
|
-
|
|
201
|
-
## Conclusion
|
|
202
|
-
|
|
203
|
-
The telescope functionality is now **correctly placed in the action hover toolbar** alongside other action tools (Calendar, Edit, Delete). The Eye icon provides:
|
|
204
|
-
|
|
205
|
-
✅ **Discoverability**: In the toolbar where users expect action tools
|
|
206
|
-
✅ **Visual Prominence**: Primary color, positioned first
|
|
207
|
-
✅ **Consistency**: Matches the pattern of other toolbar icons
|
|
208
|
-
✅ **Accessibility**: Proper tooltips and keyboard navigation
|
|
209
|
-
✅ **Functionality**: Tested and verified working
|
|
210
|
-
|
|
211
|
-
**Status: PRODUCTION READY** 🚀
|
|
212
|
-
|
|
213
|
-
Version: 1.4.0
|
|
214
|
-
Build: Successful
|
|
215
|
-
Tests: All passing
|