Skip to content

Commit d5eafe8

Browse files
akoclaude
andcommitted
docs: add multi-project tree view proposal
Design for supporting multiple Mendix projects in a single VS Code workspace. Phased approach: tree view first, then project-aware commands, LSP per project, cross-project catalog queries via SQLite ATTACH, and finally cross-project operations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 90ee695 commit d5eafe8

1 file changed

Lines changed: 233 additions & 0 deletions

File tree

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Proposal: Multi-Project Tree View
2+
3+
**Status:** Draft
4+
**Date:** 2026-04-06
5+
6+
## Motivation
7+
8+
Mendix solutions increasingly span multiple applications: a frontend and a backend, or a microservices landscape (product catalog, orders, fulfillment). Another use case is monolith-to-multi-app refactoring — start with one large app and use AI to split it into smaller apps.
9+
10+
Today the VS Code extension assumes a single `.mpr` project per workspace. Supporting multiple projects in the tree view is the first step toward multi-app development workflows.
11+
12+
## Current State
13+
14+
The single-project assumption runs through five layers:
15+
16+
| Layer | File | Assumption |
17+
|-------|------|-----------|
18+
| **Setting** | `package.json` | `mdl.mprPath` is a single `string` |
19+
| **Discovery** | `extension.ts` `findMprPath()` | Returns first `.mpr` only |
20+
| **Tree provider** | `projectTreeProvider.ts` | One `mprPath`, one `treeData[]` |
21+
| **CLI** | `project_tree.go` | Accepts one `-p` flag, returns flat array |
22+
| **LSP** | `lsp.go` | `mprPath string` on server struct |
23+
24+
## Design
25+
26+
### Principle: project as root node
27+
28+
Each `.mpr` project becomes a collapsible root node in the tree. Modules, domain models, and documents nest underneath their project. This mirrors Studio Pro's project explorer for multi-app solutions.
29+
30+
```
31+
Mendix Projects
32+
├── ProductCatalog (ProductCatalog.mpr)
33+
│ ├── Settings
34+
│ ├── Navigation
35+
│ ├── Project Security
36+
│ ├── Catalog
37+
│ │ ├── Domain Model
38+
│ │ ├── Pages
39+
│ │ └── Microflows
40+
│ └── ...
41+
├── OrderService (OrderService.mpr)
42+
│ ├── Settings
43+
│ ├── Navigation
44+
│ ├── Project Security
45+
│ ├── Orders
46+
│ │ ├── Domain Model
47+
│ │ └── Microflows
48+
│ └── ...
49+
└── Fulfillment (Fulfillment.mpr)
50+
└── ...
51+
```
52+
53+
When the workspace has only one `.mpr` file, the project root node is omitted and the tree looks exactly like today (no visual change for the common case).
54+
55+
### Changes by layer
56+
57+
#### 1. Project discovery (`extension.ts`)
58+
59+
Replace `findMprPath()` (returns first match) with `findAllMprPaths()` (returns all `.mpr` files):
60+
61+
```typescript
62+
async function findAllMprPaths(): Promise<string[]> {
63+
const config = vscode.workspace.getConfiguration('mdl');
64+
const configured = config.get<string[]>('mprPaths', []);
65+
if (configured.length > 0) {
66+
return configured;
67+
}
68+
// Auto-discover: find all .mpr files, one level deep per workspace folder
69+
const files = await vscode.workspace.findFiles('*/*.mpr', '**/node_modules/**', 20);
70+
return files.map(f => f.fsPath);
71+
}
72+
```
73+
74+
Discovery looks for `<workspace>/<app-dir>/<name>.mpr` — one level deep matches the expected layout where each project has its own subdirectory.
75+
76+
#### 2. Setting (`package.json`)
77+
78+
Add a new array setting alongside the existing one (backward compatible):
79+
80+
```jsonc
81+
"mdl.mprPaths": {
82+
"type": "array",
83+
"items": { "type": "string" },
84+
"default": [],
85+
"description": "Paths to Mendix .mpr files. If empty, auto-discovers in workspace."
86+
}
87+
```
88+
89+
The existing `mdl.mprPath` (singular) continues to work for single-project workspaces.
90+
91+
#### 3. Tree provider (`projectTreeProvider.ts`)
92+
93+
The tree provider manages multiple projects:
94+
95+
```typescript
96+
interface ProjectTree {
97+
mprPath: string;
98+
name: string; // derived from .mpr filename
99+
treeData: MendixTreeNode[];
100+
}
101+
102+
class MendixProjectTreeProvider {
103+
private projects: ProjectTree[] = [];
104+
105+
async refresh(): Promise<void> {
106+
const paths = await findAllMprPaths();
107+
this.projects = [];
108+
for (const mprPath of paths) {
109+
const treeData = await this.loadProjectTree(mprPath);
110+
this.projects.push({
111+
mprPath,
112+
name: path.basename(mprPath, '.mpr'),
113+
treeData,
114+
});
115+
}
116+
this._onDidChangeTreeData.fire(undefined);
117+
}
118+
119+
getChildren(element?: MendixTreeNode): MendixTreeNode[] {
120+
if (!element) {
121+
// Root level
122+
if (this.projects.length === 1) {
123+
// Single project: flat (same as today)
124+
return this.projects[0].treeData;
125+
}
126+
// Multiple projects: project root nodes
127+
return this.projects.map(p => ({
128+
label: p.name,
129+
type: 'project',
130+
qualifiedName: p.mprPath,
131+
children: p.treeData,
132+
}));
133+
}
134+
return element.children ?? [];
135+
}
136+
}
137+
```
138+
139+
**Key property**: Each tree node gains an implicit project context through its ancestor project node. Commands look up the tree to find which project a node belongs to.
140+
141+
#### 4. Command context
142+
143+
Tree item click handlers (DESCRIBE, context menu commands) need the project path. Store it on the node:
144+
145+
```typescript
146+
interface MendixTreeNode {
147+
label: string;
148+
type: string;
149+
qualifiedName?: string;
150+
children?: MendixTreeNode[];
151+
projectPath?: string; // NEW: which .mpr this node belongs to
152+
}
153+
```
154+
155+
The `projectPath` is set during tree construction and passed as `-p` when invoking `mxcli` commands.
156+
157+
#### 5. CLI (`project_tree.go`)
158+
159+
No changes needed. The extension calls `mxcli project-tree -p <path>` once per project and assembles the combined tree client-side. This avoids coupling the CLI to multi-project concerns.
160+
161+
#### 6. LSP server (future, out of scope)
162+
163+
The LSP server currently binds to one project. For multi-project, the extension could spawn one LSP server per project (each with its own `-p` flag). This is a larger change and is deferred — the tree view works without LSP changes since it uses `mxcli project-tree` directly.
164+
165+
## Implementation Plan
166+
167+
### Phase 1: Multi-project tree view
168+
169+
1. Add `findAllMprPaths()` to `extension.ts`
170+
2. Add `mdl.mprPaths` array setting to `package.json`
171+
3. Refactor `MendixProjectTreeProvider` to hold `ProjectTree[]`
172+
4. Add `projectPath` to `MendixTreeNode` interface
173+
5. Update `getChildren()` for single-project-flat vs multi-project-nested
174+
6. Update command handlers to read `projectPath` from tree node context
175+
7. Add `project` type icon (`symbol-namespace`)
176+
177+
### Phase 2: Project-aware commands (future)
178+
179+
8. Context menu "Open in Terminal" scoped to project directory
180+
9. DESCRIBE / SHOW commands pass correct `-p` per project
181+
10. MDL script execution targets the right project
182+
183+
### Phase 3: Multi-project LSP (future)
184+
185+
11. Spawn separate LSP server per project
186+
12. Route diagnostics/completions to correct server based on file location
187+
188+
### Phase 4: Cross-project catalog queries (future)
189+
190+
13. Use SQLite `ATTACH DATABASE` to mount each project's catalog under an alias
191+
14. Enable cross-project queries: `SELECT * FROM orders.catalog.entities JOIN catalog.catalog.entities ON ...`
192+
15. `SHOW CALLERS OF Module.Microflow ACROSS PROJECTS` for cross-project dependency analysis
193+
194+
SQLite natively supports attaching multiple databases to a single connection. Each project's catalog (built by `REFRESH CATALOG`) is a standalone `.db` file. By attaching them with project-scoped aliases, the existing `SELECT ... FROM CATALOG.*` syntax extends naturally to cross-project joins without a new query engine.
195+
196+
```sql
197+
-- Attach project catalogs
198+
ATTACH 'orders/.mxcli/catalog.db' AS orders;
199+
ATTACH 'fulfillment/.mxcli/catalog.db' AS fulfillment;
200+
201+
-- Cross-project query: which entities exist in both?
202+
SELECT o.name, f.name
203+
FROM orders.entities o
204+
JOIN fulfillment.entities f ON o.name = f.name;
205+
```
206+
207+
### Phase 5: Cross-project operations (future)
208+
209+
16. MOVE entity/microflow between projects
210+
17. Dependency visualization (which project calls which)
211+
18. Shared module extraction
212+
213+
## Single-Project Backward Compatibility
214+
215+
When only one `.mpr` is found:
216+
- Tree looks identical to today (no wrapping project node)
217+
- `mdl.mprPath` (singular) still works
218+
- All commands behave exactly as before
219+
- No new UI elements shown
220+
221+
## Scope Exclusions
222+
223+
- **Multi-project LSP**: Out of scope — deferred to Phase 3
224+
- **Cross-project references**: Out of scope — deferred to Phase 4
225+
- **Shared module management**: Out of scope
226+
- **Multi-project MDL scripts**: Out of scope (scripts already target one `-p` at a time)
227+
228+
## Effort Estimate
229+
230+
- Phase 1: Medium — ~200 lines TypeScript, no Go changes
231+
- Phase 2: Small — command handler updates
232+
- Phase 3: Medium — LSP spawning and routing
233+
- Phase 4: Large — new CLI commands and BSON operations

0 commit comments

Comments
 (0)