feat(graph): register the vault graph view in the context panel (T-323)
Wires GraphPanel into GraphExtension as the graph.view context-panel tab (its icon-rail entry and tab id were already scaffolded) and ships the builtin.graph i18n catalogs. The graph is now reachable: notes as nodes, wikilinks as edges, hover-highlight and click-to-open. It activates after its pql dependency, which supplies the link data. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,10 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
||||
|
||||
### Added
|
||||
|
||||
- **Vault graph view.** A force-directed link graph of the whole vault in the
|
||||
context panel — notes are nodes, wikilinks edges. Hover highlights a note's
|
||||
neighbourhood; click opens it in the editor. (T-323)
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tab.graph.title": { "translation": "Graph" },
|
||||
"graph.empty": { "translation": "No linked notes in this vault." }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tab.graph.title": { "translation": "Grafiek" },
|
||||
"graph.empty": { "translation": "Geen gekoppelde notities in deze vault." }
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'package:clide/builtin/graph/src/graph_panel.dart';
|
||||
import 'package:clide/extension/extension.dart';
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
|
||||
class GraphExtension extends ClideExtension {
|
||||
@override
|
||||
@@ -11,5 +14,16 @@ class GraphExtension extends ClideExtension {
|
||||
List<String> get dependsOn => const ['builtin.pql'];
|
||||
|
||||
@override
|
||||
List<ContributionPoint> get contributions => const [];
|
||||
List<ContributionPoint> get contributions => [
|
||||
TabContribution(
|
||||
id: 'graph.view',
|
||||
slot: Slots.contextPanel,
|
||||
title: 'Graph',
|
||||
titleKey: 'tab.graph.title',
|
||||
i18nNamespace: id,
|
||||
icon: PhosphorIcons.byName('graph'),
|
||||
priority: -70,
|
||||
build: (_) => const GraphPanel(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/// GraphExtension (T-323) contributes the vault-graph tab into the context
|
||||
/// panel — but only once its pql dependency is active (it reads link data
|
||||
/// through pql).
|
||||
library;
|
||||
|
||||
import 'package:clide/builtin/graph/graph.dart';
|
||||
import 'package:clide/builtin/graph/src/graph_panel.dart';
|
||||
import 'package:clide/builtin/pql/pql.dart';
|
||||
import 'package:clide/extension/extension.dart';
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../helpers/kernel_fixture.dart';
|
||||
import '../../helpers/widget_harness.dart';
|
||||
|
||||
void main() {
|
||||
late KernelFixture f;
|
||||
setUp(() async => f = await KernelFixture.create());
|
||||
tearDown(() => f.dispose());
|
||||
|
||||
test('declares one context-panel tab with the graph.view identity', () {
|
||||
final tabs = GraphExtension().contributions.whereType<TabContribution>().toList();
|
||||
expect(tabs, hasLength(1));
|
||||
final t = tabs.single;
|
||||
expect(t.id, 'graph.view');
|
||||
expect(t.slot, Slots.contextPanel);
|
||||
expect(t.title, 'Graph');
|
||||
expect(t.titleKey, 'tab.graph.title');
|
||||
expect(t.i18nNamespace, 'builtin.graph');
|
||||
});
|
||||
|
||||
testWidgets('the tab builds a GraphPanel', (tester) async {
|
||||
final tab = GraphExtension().contributions.whereType<TabContribution>().single;
|
||||
late Widget built;
|
||||
await tester.pumpWidget(
|
||||
anchoredHarness(
|
||||
f,
|
||||
Builder(
|
||||
builder: (ctx) {
|
||||
built = tab.build(ctx); // capture without mounting — no load/ipc needed
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(built, isA<GraphPanel>());
|
||||
});
|
||||
|
||||
test('registers into the context panel once activated (after its pql dep)', () async {
|
||||
f.services.extensions.register(PqlExtension());
|
||||
f.services.extensions.register(GraphExtension());
|
||||
await f.services.extensions.activate('builtin.pql');
|
||||
await f.services.extensions.activate('builtin.graph');
|
||||
expect(f.services.panels.tabsFor(Slots.contextPanel).any((t) => t.id == 'graph.view'), isTrue);
|
||||
});
|
||||
|
||||
test('is skipped when its pql dependency is not active', () async {
|
||||
f.services.extensions.register(GraphExtension());
|
||||
await f.services.extensions.activate('builtin.graph'); // dep missing → skipped
|
||||
expect(f.services.panels.tabsFor(Slots.contextPanel).any((t) => t.id == 'graph.view'), isFalse);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user