Skip to content

Commit 7619395

Browse files
author
Clawdious
committed
fix: read graph index via adapter (dotfile fix), add memory graph + observations + kanban to status view
1 parent e85e7de commit 7619395

File tree

3 files changed

+188
-15
lines changed

3 files changed

+188
-15
lines changed

src/status-view.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Sidebar panel showing vault statistics
44
*/
55

6-
import { ItemView, WorkspaceLeaf } from "obsidian";
6+
import { ItemView, TFile, WorkspaceLeaf } from "obsidian";
77
import type ClawVaultPlugin from "./main";
88
import { COMMAND_IDS, STATUS_VIEW_TYPE } from "./constants";
99
import type { ObservationSession, ParsedTask, VaultStats } from "./vault-reader";
@@ -13,6 +13,8 @@ interface StatusViewData {
1313
backlogItems: ParsedTask[];
1414
recentSessions: ObservationSession[];
1515
openLoops: ParsedTask[];
16+
graphTypes: Record<string, number>;
17+
todayObs: { count: number; categories: string[] };
1618
}
1719

1820
/**
@@ -63,17 +65,21 @@ export class ClawVaultStatusView extends ItemView {
6365
this.statusContentEl.empty();
6466

6567
try {
66-
const [stats, backlogItems, recentSessions, openLoops] = await Promise.all([
68+
const [stats, backlogItems, recentSessions, openLoops, graphTypes, todayObs] = await Promise.all([
6769
this.plugin.vaultReader.getVaultStats(),
6870
this.plugin.vaultReader.getBacklogTasks(5),
6971
this.plugin.vaultReader.getRecentObservationSessions(5),
7072
this.plugin.vaultReader.getOpenLoops(7),
73+
this.plugin.vaultReader.getGraphTypeSummary(),
74+
this.plugin.vaultReader.getTodayObservations(),
7175
]);
7276
this.renderStats({
7377
stats,
7478
backlogItems,
7579
recentSessions,
7680
openLoops,
81+
graphTypes,
82+
todayObs,
7783
});
7884
} catch (error) {
7985
this.renderError(error);
@@ -85,7 +91,7 @@ export class ClawVaultStatusView extends ItemView {
8591
*/
8692
private renderStats(data: StatusViewData): void {
8793
if (!this.statusContentEl) return;
88-
const { stats, backlogItems, recentSessions, openLoops } = data;
94+
const { stats, backlogItems, recentSessions, openLoops, graphTypes, todayObs } = data;
8995

9096
// Header
9197
const header = this.statusContentEl.createDiv({ cls: "clawvault-status-header" });
@@ -103,6 +109,60 @@ export class ClawVaultStatusView extends ItemView {
103109
cls: "clawvault-status-counts",
104110
});
105111

112+
// Memory Graph section
113+
if (stats.nodeCount > 0 || Object.keys(graphTypes).length > 0) {
114+
const graphSection = this.statusContentEl.createDiv({ cls: "clawvault-status-section" });
115+
graphSection.createEl("h4", { text: "Memory Graph" });
116+
117+
const graphStats = graphSection.createDiv({ cls: "clawvault-graph-stats" });
118+
graphStats.createDiv({
119+
text: `🔗 ${this.formatNumber(stats.nodeCount)} nodes · ${this.formatNumber(stats.edgeCount)} edges`,
120+
cls: "clawvault-graph-totals",
121+
});
122+
123+
// Node type breakdown (top 5)
124+
const sortedTypes = Object.entries(graphTypes)
125+
.sort((a, b) => b[1] - a[1])
126+
.slice(0, 6);
127+
if (sortedTypes.length > 0) {
128+
const typeGrid = graphSection.createDiv({ cls: "clawvault-graph-type-grid" });
129+
for (const [type, count] of sortedTypes) {
130+
const typeEl = typeGrid.createDiv({ cls: "clawvault-graph-type-item" });
131+
typeEl.createSpan({ text: `${count}`, cls: "clawvault-graph-type-count" });
132+
typeEl.createSpan({ text: ` ${type}`, cls: "clawvault-graph-type-label" });
133+
}
134+
}
135+
}
136+
137+
// Today's observations
138+
if (todayObs.count > 0) {
139+
const obsSection = this.statusContentEl.createDiv({ cls: "clawvault-status-section" });
140+
obsSection.createDiv({
141+
text: `🔭 ${todayObs.count} observation${todayObs.count === 1 ? "" : "s"} today`,
142+
cls: "clawvault-obs-today",
143+
});
144+
if (todayObs.categories.length > 0) {
145+
obsSection.createDiv({
146+
text: `→ ${todayObs.categories.join(", ")}`,
147+
cls: "clawvault-obs-categories",
148+
});
149+
}
150+
}
151+
152+
// Kanban board link
153+
const boardFile = this.app.vault.getAbstractFileByPath("Board.md");
154+
if (boardFile instanceof TFile) {
155+
const kanbanSection = this.statusContentEl.createDiv({ cls: "clawvault-status-section" });
156+
const kanbanLink = kanbanSection.createEl("a", {
157+
text: "📋 Open Kanban Board",
158+
cls: "clawvault-kanban-link",
159+
});
160+
kanbanLink.addEventListener("click", (event) => {
161+
event.preventDefault();
162+
void this.app.workspace.openLinkText("Board.md", "", "tab");
163+
});
164+
}
165+
106166
// Tasks section
107167
if (stats.tasks.total > 0) {
108168
const tasksSection = this.statusContentEl.createDiv({ cls: "clawvault-status-section" });

src/vault-reader.ts

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,20 @@ export class VaultReader {
117117
return Date.now() - this.lastCacheTime < this.cacheTimeout;
118118
}
119119

120+
/**
121+
* Read a file using the adapter (bypasses Obsidian's dotfile filter)
122+
*/
123+
private async readFileByAdapter(path: string): Promise<string | null> {
124+
try {
125+
if (await this.app.vault.adapter.exists(path)) {
126+
return await this.app.vault.adapter.read(path);
127+
}
128+
} catch (error) {
129+
console.warn(`ClawVault: Could not read ${path}:`, error);
130+
}
131+
return null;
132+
}
133+
120134
/**
121135
* Read and parse .clawvault.json
122136
*/
@@ -125,16 +139,15 @@ export class VaultReader {
125139
return this.cachedConfig;
126140
}
127141

128-
try {
129-
const file = this.app.vault.getAbstractFileByPath(CLAWVAULT_CONFIG_FILE);
130-
if (file instanceof TFile) {
131-
const content = await this.app.vault.read(file);
142+
const content = await this.readFileByAdapter(CLAWVAULT_CONFIG_FILE);
143+
if (content) {
144+
try {
132145
this.cachedConfig = JSON.parse(content) as ClawVaultConfig;
133146
this.lastCacheTime = Date.now();
134147
return this.cachedConfig;
148+
} catch (error) {
149+
console.warn("ClawVault: Could not parse config:", error);
135150
}
136-
} catch (error) {
137-
console.warn("ClawVault: Could not read config file:", error);
138151
}
139152
return null;
140153
}
@@ -147,20 +160,71 @@ export class VaultReader {
147160
return this.cachedGraphIndex;
148161
}
149162

150-
try {
151-
const file = this.app.vault.getAbstractFileByPath(CLAWVAULT_GRAPH_INDEX);
152-
if (file instanceof TFile) {
153-
const content = await this.app.vault.read(file);
163+
const content = await this.readFileByAdapter(CLAWVAULT_GRAPH_INDEX);
164+
if (content) {
165+
try {
154166
this.cachedGraphIndex = JSON.parse(content) as GraphIndex;
155167
this.lastCacheTime = Date.now();
156168
return this.cachedGraphIndex;
169+
} catch (error) {
170+
console.warn("ClawVault: Could not parse graph index:", error);
157171
}
158-
} catch (error) {
159-
console.warn("ClawVault: Could not read graph index:", error);
160172
}
161173
return null;
162174
}
163175

176+
/**
177+
* Read today's observation files
178+
*/
179+
async getTodayObservations(): Promise<{ count: number; categories: string[] }> {
180+
const todayStr = new Date().toISOString().split("T")[0] ?? "";
181+
if (!todayStr) return { count: 0, categories: [] };
182+
183+
const categories = new Set<string>();
184+
let count = 0;
185+
const todayStart = new Date(todayStr).getTime();
186+
187+
// Check ledger/observations for today's files
188+
const ledgerPath = "ledger/observations";
189+
try {
190+
const folder = this.app.vault.getAbstractFileByPath(ledgerPath);
191+
if (folder instanceof TFolder) {
192+
for (const child of folder.children) {
193+
if (child instanceof TFile && child.basename.startsWith(todayStr)) {
194+
count++;
195+
}
196+
}
197+
}
198+
} catch { /* no ledger dir */ }
199+
200+
// Check observations folder for today's files
201+
const obsFiles = this.getFilesInFolder("observations");
202+
for (const f of obsFiles) {
203+
if (f.basename.startsWith(todayStr) || f.stat.mtime >= todayStart) {
204+
count++;
205+
const parts = f.path.split("/");
206+
if (parts.length > 2 && parts[1]) categories.add(parts[1]);
207+
}
208+
}
209+
210+
return { count, categories: Array.from(categories) };
211+
}
212+
213+
/**
214+
* Get graph stats summary by node type
215+
*/
216+
async getGraphTypeSummary(): Promise<Record<string, number>> {
217+
const graph = await this.readGraphIndex();
218+
if (!graph) return {};
219+
220+
const typeCounts: Record<string, number> = {};
221+
for (const node of graph.nodes) {
222+
const type = node.type ?? "unknown";
223+
typeCounts[type] = (typeCounts[type] ?? 0) + 1;
224+
}
225+
return typeCounts;
226+
}
227+
164228
/**
165229
* Get all markdown files in a folder
166230
*/

styles.css

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,3 +688,52 @@
688688
.theme-dark .clawvault-task-textarea {
689689
background-color: var(--background-secondary);
690690
}
691+
692+
/* Memory Graph section */
693+
.clawvault-graph-totals {
694+
font-size: 0.9em;
695+
margin-bottom: 6px;
696+
}
697+
698+
.clawvault-graph-type-grid {
699+
display: grid;
700+
grid-template-columns: 1fr 1fr;
701+
gap: 2px 12px;
702+
font-size: 0.8em;
703+
opacity: 0.8;
704+
}
705+
706+
.clawvault-graph-type-count {
707+
font-weight: 600;
708+
color: var(--text-accent);
709+
}
710+
711+
.clawvault-graph-type-label {
712+
color: var(--text-muted);
713+
}
714+
715+
/* Observations today */
716+
.clawvault-obs-today {
717+
font-size: 0.9em;
718+
}
719+
720+
.clawvault-obs-categories {
721+
font-size: 0.8em;
722+
color: var(--text-muted);
723+
margin-top: 2px;
724+
}
725+
726+
/* Kanban board link */
727+
.clawvault-kanban-link {
728+
display: block;
729+
padding: 6px 10px;
730+
background: var(--background-modifier-hover);
731+
border-radius: 4px;
732+
text-align: center;
733+
cursor: pointer;
734+
font-size: 0.9em;
735+
}
736+
737+
.clawvault-kanban-link:hover {
738+
background: var(--background-modifier-active-hover);
739+
}

0 commit comments

Comments
 (0)