iikit-dashboard 1.4.0 → 1.5.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.
@@ -2,11 +2,11 @@
2
2
  'use strict';
3
3
 
4
4
  const path = require('path');
5
- const { createServer } = require('../src/server');
5
+ const { createServer, removePidfile } = require('../src/server');
6
6
 
7
7
  // Parse arguments
8
8
  const args = process.argv.slice(2);
9
- let projectPath = process.cwd();
9
+ let projectPath = path.resolve(process.cwd());
10
10
  let port = 3000;
11
11
 
12
12
  for (let i = 0; i < args.length; i++) {
@@ -46,6 +46,7 @@ async function main() {
46
46
  // Handle graceful shutdown
47
47
  process.on('SIGINT', () => {
48
48
  console.log('\n Shutting down...');
49
+ removePidfile(projectPath);
49
50
  result.server.close(() => {
50
51
  if (result.watcher) result.watcher.close();
51
52
  process.exit(0);
@@ -53,6 +54,7 @@ async function main() {
53
54
  });
54
55
 
55
56
  process.on('SIGTERM', () => {
57
+ removePidfile(projectPath);
56
58
  result.server.close(() => {
57
59
  if (result.watcher) result.watcher.close();
58
60
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iikit-dashboard",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Real-time dashboard for Intent Integrity Kit (IIKit) — visualizes every phase of specification-driven AI development",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -93,6 +93,17 @@
93
93
  flex: 1 1 auto;
94
94
  }
95
95
 
96
+ .project-label {
97
+ font-size: 12px;
98
+ color: var(--color-text-muted);
99
+ max-width: 160px;
100
+ overflow: hidden;
101
+ text-overflow: ellipsis;
102
+ white-space: nowrap;
103
+ border-left: 1px solid var(--color-border);
104
+ padding-left: 12px;
105
+ }
106
+
96
107
  .feature-selector {
97
108
  position: relative;
98
109
  min-width: 100px;
@@ -2368,6 +2379,7 @@
2368
2379
  <div class="logo-icon" aria-hidden="true">D</div>
2369
2380
  <span>IIKit Dashboard</span>
2370
2381
  </div>
2382
+ <div class="project-label" id="projectLabel" title=""></div>
2371
2383
  <div class="feature-selector" role="navigation" aria-label="Feature selector">
2372
2384
  <select id="featureSelect" aria-label="Select feature to display" tabindex="0">
2373
2385
  <option value="">Loading features...</option>
@@ -5188,6 +5200,12 @@
5188
5200
  applyTheme(themeMode);
5189
5201
 
5190
5202
  // ====== Init ======
5203
+ fetch('/api/meta').then(r => r.json()).then(meta => {
5204
+ const label = document.getElementById('projectLabel');
5205
+ const dirName = meta.projectPath.split('/').pop();
5206
+ label.textContent = dirName;
5207
+ label.title = meta.projectPath;
5208
+ }).catch(() => {});
5191
5209
  loadFeatures();
5192
5210
  connectWebSocket();
5193
5211
  })();
package/src/server.js CHANGED
@@ -98,6 +98,33 @@ function getBoardState(projectPath, featureId) {
98
98
  return { ...board, integrity };
99
99
  }
100
100
 
101
+ /**
102
+ * Write a pidfile with metadata so external scripts can identify this dashboard instance.
103
+ */
104
+ function writePidfile(projectPath, port) {
105
+ const resolved = path.resolve(projectPath);
106
+ const specifyDir = path.join(resolved, '.specify');
107
+ fs.mkdirSync(specifyDir, { recursive: true });
108
+ const pidData = {
109
+ pid: process.pid,
110
+ port,
111
+ directory: resolved,
112
+ startedAt: new Date().toISOString()
113
+ };
114
+ fs.writeFileSync(path.join(specifyDir, 'dashboard.pid.json'), JSON.stringify(pidData, null, 2));
115
+ }
116
+
117
+ /**
118
+ * Remove the pidfile on shutdown.
119
+ */
120
+ function removePidfile(projectPath) {
121
+ try {
122
+ fs.unlinkSync(path.join(path.resolve(projectPath), '.specify', 'dashboard.pid.json'));
123
+ } catch (err) {
124
+ if (err.code !== 'ENOENT') throw err;
125
+ }
126
+ }
127
+
101
128
  /**
102
129
  * Create and configure the Express server with WebSocket support.
103
130
  *
@@ -107,11 +134,17 @@ function getBoardState(projectPath, featureId) {
107
134
  * @returns {Promise<{server: http.Server, port: number, wss: WebSocketServer}>}
108
135
  */
109
136
  function createServer({ projectPath, port = 3000 }) {
137
+ const resolvedPath = path.resolve(projectPath);
110
138
  const app = express();
111
139
 
112
140
  // Serve static files from src/public
113
141
  app.use(express.static(path.join(__dirname, 'public')));
114
142
 
143
+ // API: project metadata
144
+ app.get('/api/meta', (req, res) => {
145
+ res.json({ projectPath: resolvedPath });
146
+ });
147
+
115
148
  // API: list features
116
149
  app.get('/api/features', (req, res) => {
117
150
  try {
@@ -363,9 +396,10 @@ function createServer({ projectPath, port = 3000 }) {
363
396
  return new Promise((resolve) => {
364
397
  server.listen(port, () => {
365
398
  const actualPort = server.address().port;
366
- resolve({ server, port: actualPort, wss, watcher });
399
+ writePidfile(resolvedPath, actualPort);
400
+ resolve({ server, port: actualPort, wss, watcher, projectPath: resolvedPath });
367
401
  });
368
402
  });
369
403
  }
370
404
 
371
- module.exports = { createServer, listFeatures, getBoardState };
405
+ module.exports = { createServer, listFeatures, getBoardState, removePidfile };