drafted 1.19.41 → 1.19.43

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.
Files changed (2) hide show
  1. package/mcp/server.mjs +29 -11
  2. package/package.json +1 -1
package/mcp/server.mjs CHANGED
@@ -1368,6 +1368,14 @@ function enforceTokenBudget(text, max = MAX_TOOL_RESULT_CHARS) {
1368
1368
  return head + tail;
1369
1369
  }
1370
1370
 
1371
+ // Mirrors projectIsTrashed in src/db/queries.ts and isTrashed() in home-html.mjs:
1372
+ // a project is out of the active listing if it has a trashedAt clock OR carries
1373
+ // the legacy '__archived' folder sentinel. Both are checked because pre-Trash
1374
+ // rows still hold the sentinel and were deliberately not migrated onto a clock.
1375
+ function isTrashedRow(x) {
1376
+ return !!(x && (x.trashedAt || x.folder === '__archived'));
1377
+ }
1378
+
1371
1379
  function ok(text, opts) {
1372
1380
  const str = typeof text === 'string' ? text : JSON.stringify(text, null, 2);
1373
1381
  const result = { content: [{ type: 'text', text: enforceTokenBudget(str) }] };
@@ -3474,7 +3482,7 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
3474
3482
  // the row while wiki/skills used the form the caller typed — one result,
3475
3483
  // two spellings of one org.
3476
3484
  const projectRows = (Array.isArray(projects.v?.projects) ? projects.v.projects : [])
3477
- .filter(x => x.folder !== '__archived');
3485
+ .filter(x => !isTrashedRow(x));
3478
3486
  const folderById = new Map(projectRows.map(x => [x.id, x.folder || '']));
3479
3487
  const orgAs = canonicalOrg;
3480
3488
  section('Projects', projects, () => {
@@ -3821,7 +3829,7 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
3821
3829
  // active list — mirroring the web UI. An org-scoped root lists ONLY that
3822
3830
  // org's projects (Shape A).
3823
3831
  const listing = await api('GET', '/api/projects');
3824
- let rows = (Array.isArray(listing?.projects) ? listing.projects : []).filter(x => x.folder !== '__archived');
3832
+ let rows = (Array.isArray(listing?.projects) ? listing.projects : []).filter(x => !isTrashedRow(x));
3825
3833
  if (orgFromPath) {
3826
3834
  const kept = [];
3827
3835
  for (const x of rows) {
@@ -4147,27 +4155,37 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
4147
4155
  return ok(result);
4148
4156
  }
4149
4157
  case 'rm': {
4150
- // Project-level rm = archive (soft delete): move to the reserved
4151
- // __archived folder. Frame/lane rm still deletes those items, but a
4152
- // whole project is never hard-deleted by an agent — the web UI's
4153
- // Archive bin is where a human permanently deletes.
4158
+ // Project-level rm = move to Trash (soft delete). Frame/lane rm still
4159
+ // deletes those items outright, but a whole project is never
4160
+ // hard-deleted by an agent — a human does that from the Trash bin, or
4161
+ // the server's purge sweep does it after the retention window.
4162
+ // It now goes through DELETE (which trashes unless ?permanent=1) rather
4163
+ // than PATCHing folder='__archived': the sentinel overwrote the
4164
+ // project's real folder, so restoring dumped it at the org root.
4154
4165
  // A layer or lane path has no filename, so it used to fall through to
4155
4166
  // the archive branch and archive the WHOLE project — with a
4156
4167
  // success-shaped response. /slides/deck and /slides are one keystroke
4157
- // from the project path; only the bare project path may archive.
4168
+ // from the project path; only the bare project path may trash.
4158
4169
  const scope = rmScope(layer, lane, filename);
4159
4170
  if (scope === 'directory') {
4160
4171
  return err(new Error(
4161
4172
  `rm on a ${lane ? 'lane' : 'layer'} path deletes nothing — Drafted has no directory delete. ` +
4162
4173
  `Remove the frames individually: /projects/<project>/${layer}${lane ? '/' + lane : ''}/<file>. ` +
4163
- `To archive the whole project, address the project itself: /projects/<project>`
4174
+ `To trash the whole project, address the project itself: /projects/<project>`
4164
4175
  ));
4165
4176
  }
4166
4177
  if (scope === 'project') {
4167
4178
  const pid = getState().projectId;
4168
- if (!pid) return err(new Error('could not resolve project id for archive'));
4169
- const result = await api('PATCH', `/api/project/${pid}`, { folder: '__archived' }, orgHeader);
4170
- return ok({ archived: true, project: projectRef, projectId: pid, archiveHint: 'The project now lives in the Archive bin in the web UI sidebar — a human can restore or permanently delete it there.' });
4179
+ if (!pid) return err(new Error('could not resolve project id for trash'));
4180
+ const result = await api('DELETE', `/api/project/${pid}`, undefined, orgHeader);
4181
+ const days = result?.retentionDays ?? 30;
4182
+ return ok({
4183
+ trashed: true,
4184
+ project: projectRef,
4185
+ projectId: pid,
4186
+ retentionDays: days,
4187
+ trashHint: `The project is in the Trash bin in the web UI sidebar. It keeps its folder and can be restored there for ${days} days, after which it is deleted permanently.`,
4188
+ });
4171
4189
  }
4172
4190
  const rmPath = lane ? `/${layer}/${lane}/${filename}` : `/${layer}/${filename}`;
4173
4191
  const result = await api('DELETE', `/api/fs/${rmPath.replace(/^\//, '')}`, undefined, orgHeader);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.19.41",
3
+ "version": "1.19.43",
4
4
  "description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
5
5
  "type": "module",
6
6
  "files": [