@promovaweb/specsfy 0.8.1 → 0.9.2

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.
@@ -1,7 +1,6 @@
1
1
  /** Diagnóstico compartilhado pelos fluxos de setup, update e upgrade. */
2
2
  import { constants } from "node:fs";
3
- import { access, realpath, stat } from "node:fs/promises";
4
- import { createRequire } from "node:module";
3
+ import { access, stat } from "node:fs/promises";
5
4
  import { delimiter, join, resolve } from "node:path";
6
5
  const MINIMUM_NODE_VERSION = [22, 20, 0];
7
6
  /** Informa se a versão do runtime satisfaz o contrato do pacote. */
@@ -17,24 +16,18 @@ export function nodeVersionSupported(version) {
17
16
  return true;
18
17
  }
19
18
  /**
20
- * Resolve o comando oficial `skills` sem depender apenas do PATH do processo.
19
+ * Resolve o `npx` que chama a CLI oficial `skills` sob demanda.
21
20
  *
22
- * A instalação incluída no pacote cobre launchers gráficos e chamadas por
23
- * caminho absoluto, que podem omitir o diretório global do npm no PATH.
21
+ * Toda materialização do Specsfy passa por `npx skills add`, para usar a
22
+ * versão publicada e tornar o comando apresentado à pessoa explícito.
24
23
  */
25
- export async function resolveSkillsCommand(path = process.env.PATH ?? "", override = process.env.SPECSFY_SKILLS_CLI, launcher = process.argv[1]) {
24
+ export async function resolveNpxSkillsCommand(path = process.env.PATH ?? "", override = process.env.SPECSFY_NPX_COMMAND, _launcher = process.argv[1]) {
26
25
  if (override) {
27
26
  const executable = resolve(override);
28
- return (await isExecutable(executable)) ? [executable] : undefined;
27
+ return (await isExecutable(executable)) ? [executable, "skills"] : undefined;
29
28
  }
30
- const skills = await findExecutable("skills", path);
31
- if (skills)
32
- return [skills];
33
- const packaged = await resolvePackagedSkills(launcher);
34
- if (packaged)
35
- return [process.execPath, packaged];
36
29
  const npx = await findExecutable("npx", path);
37
- return npx ? [npx, "--yes", "skills"] : undefined;
30
+ return npx ? [npx, "skills"] : undefined;
38
31
  }
39
32
  /** Verifica todos os recursos usados pelos comandos de instalação e atualização. */
40
33
  export async function checkPrerequisites(project, options = {}) {
@@ -42,7 +35,7 @@ export async function checkPrerequisites(project, options = {}) {
42
35
  const nodeVersion = options.nodeVersion ?? process.versions.node;
43
36
  const git = await findExecutable("git", path);
44
37
  const npm = await findExecutable("npm", path);
45
- const skills = await resolveSkillsCommand(path, options.skillsOverride ?? process.env.SPECSFY_SKILLS_CLI, options.skillsLauncher ?? process.argv[1]);
38
+ const skills = await resolveNpxSkillsCommand(path, options.npxOverride ?? process.env.SPECSFY_NPX_COMMAND, options.skillsLauncher ?? process.argv[1]);
46
39
  const target = resolve(project);
47
40
  let projectOk = false;
48
41
  try {
@@ -69,9 +62,8 @@ export async function checkPrerequisites(project, options = {}) {
69
62
  name: "skills",
70
63
  ok: Boolean(skills),
71
64
  detail: skills
72
- ? `skills CLI disponível por ${skills.join(" ")}`
73
- : "skills CLI ausente; reinstale `@promovaweb/specsfy` pelo npm ou " +
74
- "disponibilize `skills` ou `npx` no PATH",
65
+ ? `instalação de skills disponível por ${skills.join(" ")}`
66
+ : "npx não encontrado; instale o npm e disponibilize `npx` no PATH",
75
67
  command: skills,
76
68
  },
77
69
  {
@@ -108,20 +100,6 @@ async function findExecutable(name, path) {
108
100
  }
109
101
  return undefined;
110
102
  }
111
- /** Localiza a dependência `skills` a partir do launcher instalado do Specsfy. */
112
- async function resolvePackagedSkills(launcher) {
113
- if (!launcher)
114
- return undefined;
115
- try {
116
- const require = createRequire(await realpath(resolve(launcher)));
117
- const executable = require.resolve("skills/bin/cli.mjs");
118
- await access(executable, constants.R_OK);
119
- return executable;
120
- }
121
- catch {
122
- return undefined;
123
- }
124
- }
125
103
  async function isExecutable(path) {
126
104
  try {
127
105
  await access(path, constants.X_OK);
@@ -1 +1 @@
1
- {"version":3,"file":"prerequisites.js","sourceRoot":"","sources":["../src/prerequisites.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prerequisites.js","sourceRoot":"","sources":["../src/prerequisites.ts"],"names":[],"mappings":"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"}
package/dist/tui.d.ts CHANGED
@@ -119,6 +119,10 @@ export declare class SpecsfyTui {
119
119
  private mountShell;
120
120
  private bindKeys;
121
121
  private showTab;
122
+ /** Recupera um alvo navegável quando um painel foi recriado ou fechado. */
123
+ private focusActiveTab;
124
+ /** Renderiza somente enquanto a tela ainda pertence ao ciclo atual. */
125
+ private renderScreen;
122
126
  /**
123
127
  * Retorna um nível por vez: fecha modal, limpa busca e volta ao Home.
124
128
  *
@@ -135,6 +139,7 @@ export declare class SpecsfyTui {
135
139
  private renderSkills;
136
140
  private renderAbout;
137
141
  private refreshAll;
142
+ private refreshAllInternal;
138
143
  private refreshIfChanged;
139
144
  private skillOptions;
140
145
  private visibleSkills;
package/dist/tui.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,OAAO,MAAM,aAAa,CAAC;AAQlC,OAAO,EAAE,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAe/D,OAAO,EAAmB,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAYrE,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkB9D,CAAC;AAmBF,qEAAqE;AACrE,eAAO,MAAM,YAAY;aACvB,KAAK,EAAE,MAAM;aACb,MAAM,EAAE,QAAQ;aAChB,KAAK,EAAE,WAAW;aAClB,KAAK,EAAE,WAAW;aAClB,KAAK,EAAE,UAAU;aACjB,KAAK,EAAE,UAAU;aACjB,KAAK,EAAE,gBAAgB;aACvB,KAAK,EAAE,SAAS;aAChB,KAAK,EAAE,kBAAkB;aACzB,KAAK,EAAE,QAAQ;aACf,KAAK,EAAE,QAAQ;aACf,KAAK,EAAE,OAAO;aACd,KAAK,EAAE,YAAY;aACnB,KAAK,EAAE,cAAc;aACrB,KAAK,EAAE,MAAM;aACb,KAAK,EAAE,UAAU;aACjB,KAAK,EAAE,OAAO;aACd,KAAK,EAAE,QAAQ;aACf,KAAK,EAAE,iBAAiB;aACxB,KAAK,EAAE,QAAQ;aACf,KAAK,EAAE,OAAO;CACN,CAAC;AAEX,4DAA4D;AAC5D,eAAO,MAAM,QAAQ;aACjB,EAAE,EAAE,MAAM;aAAE,KAAK,EAAE,MAAM;aAAE,GAAG,EAAE,KAAK;;aACrC,EAAE,EAAE,UAAU;aAAE,KAAK,EAAE,UAAU;aAAE,GAAG,EAAE,KAAK;;aAC7C,EAAE,EAAE,OAAO;aAAE,KAAK,EAAE,OAAO;aAAE,GAAG,EAAE,KAAK;;aACvC,EAAE,EAAE,OAAO;aAAE,KAAK,EAAE,QAAQ;aAAE,GAAG,EAAE,KAAK;;aACxC,EAAE,EAAE,QAAQ;aAAE,KAAK,EAAE,QAAQ;aAAE,GAAG,EAAE,KAAK;;aACzC,EAAE,EAAE,OAAO;aAAE,KAAK,EAAE,OAAO;aAAE,GAAG,EAAE,KAAK;EACjC,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,SAAS;aACpB,UAAU,EAAE,SAAS;aACrB,OAAO,EAAE,SAAS;aAClB,aAAa,EAAE,SAAS;aACxB,IAAI,EAAE,SAAS;aACf,SAAS,EAAE,SAAS;aACpB,MAAM,EAAE,SAAS;aACjB,MAAM,EAAE,SAAS;aACjB,gBAAgB,EAAE,SAAS;aAC3B,UAAU,EAAE,SAAS;aACrB,kBAAkB,EAAE,SAAS;aAC7B,YAAY,EAAE,SAAS;aACvB,iBAAiB,EAAE,SAAS;aAC5B,WAAW,EAAE,SAAS;aACtB,eAAe,EAAE,SAAS;aAC1B,SAAS,EAAE,SAAS;aACpB,OAAO,EAAE,SAAS;CACV,CAAC;AAEX,+DAA+D;AAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAElD,4DAA4D;AAC5D,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,WAAW,GAAG,UAAU,CAAC;AAE3D,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,8CAA8C;AAC9C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,oCAAoC;AACpC,qBAAa,UAAU;;IACrB,OAAO,EAAE,MAAM,CAAC;IAkChB,YAAY,OAAO,SAAgB,EAElC;IAED,gFAAgF;IAChF,IAAI,SAAS,IAAI,GAAG,CAEnB;IAED,kEAAkE;IAC5D,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAkBzB;IAED;;;;;OAKG;IACG,KAAK,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCxD;IAED,OAAO,CAAC,UAAU;IAmIlB,OAAO,CAAC,QAAQ;IAoChB,OAAO,CAAC,OAAO;IAwBf,2EAA2E;IAC3E,OAAO,CAAC,cAAc;IAQtB,uEAAuE;IACvE,OAAO,CAAC,YAAY;IAIpB;;;;;OAKG;IACH,OAAO,CAAC,MAAM;IAsBd,OAAO,CAAC,UAAU;IA0DlB,OAAO,CAAC,cAAc;IA2DtB,OAAO,CAAC,WAAW;IA6DnB,OAAO,CAAC,QAAQ;IAqChB,OAAO,CAAC,WAAW;YAqGL,QAAQ;IAuCtB,OAAO,CAAC,YAAY;IA6MpB,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,UAAU;YAUJ,kBAAkB;YA8ClB,gBAAgB;IAe9B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,eAAe;YAST,YAAY;IAuB1B,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,YAAY;YAQN,WAAW;YAmDX,YAAY;IAkB1B,OAAO,CAAC,UAAU;CAInB;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,iBAAiB,EAAE,EACnC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,WAAW,EAAE,CAuCf;AAED,oEAAoE;AACpE,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GACpB,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAG/C;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EAAE,EACtB,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GACpB,WAAW,EAAE,CAYf;AAED,qDAAqD;AACrD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,EACrB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GACpB,MAAM,CAUR;AAyCD,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAUrD;AAMD,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,CAG/D;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAS5D"}
package/dist/tui.js CHANGED
@@ -119,6 +119,7 @@ export class SpecsfyTui {
119
119
  #status;
120
120
  #projectInput;
121
121
  #modal;
122
+ #modalFocus;
122
123
  #lastBackAt = 0;
123
124
  #tabButtons = new Map();
124
125
  #activeTab = "home";
@@ -129,6 +130,8 @@ export class SpecsfyTui {
129
130
  #catalogError = "";
130
131
  #installed = new Set();
131
132
  #selected = new Set();
133
+ #selectionDirty = false;
134
+ #loaded = false;
132
135
  #detected = new Set();
133
136
  #skillFilter = "all";
134
137
  #skillQuery = "";
@@ -136,10 +139,13 @@ export class SpecsfyTui {
136
139
  #suppressSkillToggleUntil = 0;
137
140
  #fingerprints = { specs: "", backlogs: "", lock: "" };
138
141
  #poller;
142
+ #refreshPromise;
139
143
  #testRunning = false;
140
144
  #testResultTab = "summary";
141
145
  #testSummary = "Nenhuma execução nesta sessão.";
142
146
  #testOutput = [];
147
+ #testSummaryPanel;
148
+ #testOutputPanel;
143
149
  constructor(project = process.cwd()) {
144
150
  this.project = resolvePath(project);
145
151
  }
@@ -158,6 +164,10 @@ export class SpecsfyTui {
158
164
  });
159
165
  await this.start({ screen });
160
166
  await new Promise((resolve) => {
167
+ if (screenDestroyed(screen)) {
168
+ resolve();
169
+ return;
170
+ }
161
171
  screen.once("destroy", () => {
162
172
  resolve();
163
173
  });
@@ -170,6 +180,9 @@ export class SpecsfyTui {
170
180
  * terminais virtuais, sem manter um processo pendurado durante os testes.
171
181
  */
172
182
  async start(options = {}) {
183
+ if (this.#screen && !screenDestroyed(this.#screen)) {
184
+ throw new Error("a TUI já foi iniciada nesta instância");
185
+ }
173
186
  const screen = options.screen ??
174
187
  blessed.screen({
175
188
  smartCSR: true,
@@ -187,13 +200,20 @@ export class SpecsfyTui {
187
200
  if (this.#poller)
188
201
  clearInterval(this.#poller);
189
202
  this.#poller = undefined;
203
+ this.#refreshPromise = undefined;
204
+ this.#modal = undefined;
205
+ this.#modalFocus = undefined;
190
206
  });
191
207
  this.showStatus("Carregando projeto…");
192
208
  await this.refreshAll(!options.catalog);
193
209
  this.showTab("home");
194
210
  if (options.watch !== false) {
195
211
  const config = await loadConfig(this.project);
196
- this.#poller = setInterval(() => void this.refreshIfChanged(), config.watch_interval * 1000);
212
+ if (screenDestroyed(screen))
213
+ return;
214
+ this.#poller = setInterval(() => void this.refreshIfChanged().catch((error) => {
215
+ this.showStatus(`Erro ao atualizar automaticamente: ${errorMessage(error)}`);
216
+ }), config.watch_interval * 1000);
197
217
  }
198
218
  }
199
219
  mountShell(screen) {
@@ -378,6 +398,8 @@ export class SpecsfyTui {
378
398
  }
379
399
  for (const child of [...body.children])
380
400
  child.destroy();
401
+ this.#testSummaryPanel = undefined;
402
+ this.#testOutputPanel = undefined;
381
403
  if (tab === "home")
382
404
  this.renderHome(body);
383
405
  else if (tab === "backlogs")
@@ -390,7 +412,24 @@ export class SpecsfyTui {
390
412
  this.renderSkills(body);
391
413
  else
392
414
  this.renderAbout(body);
393
- this.#screen.render();
415
+ this.focusActiveTab();
416
+ if (!screenDestroyed(this.#screen))
417
+ this.#screen.render();
418
+ }
419
+ /** Recupera um alvo navegável quando um painel foi recriado ou fechado. */
420
+ focusActiveTab() {
421
+ const screen = this.#screen;
422
+ if (!screen || screenDestroyed(screen))
423
+ return;
424
+ const focused = screen.focused;
425
+ if (focused && !focused.detached && focused.visible)
426
+ return;
427
+ this.#tabButtons.get(this.#activeTab)?.focus();
428
+ }
429
+ /** Renderiza somente enquanto a tela ainda pertence ao ciclo atual. */
430
+ renderScreen() {
431
+ if (this.#screen && !screenDestroyed(this.#screen))
432
+ this.#screen.render();
394
433
  }
395
434
  /**
396
435
  * Retorna um nível por vez: fecha modal, limpa busca e volta ao Home.
@@ -406,7 +445,13 @@ export class SpecsfyTui {
406
445
  if (this.#modal) {
407
446
  this.#modal.destroy();
408
447
  this.#modal = undefined;
409
- this.#screen?.render();
448
+ const focus = this.#modalFocus;
449
+ this.#modalFocus = undefined;
450
+ if (focus && !focus.detached && focus.visible)
451
+ focus.focus();
452
+ else
453
+ this.focusActiveTab();
454
+ this.renderScreen();
410
455
  return;
411
456
  }
412
457
  if (this.#activeTab === "skills" && this.#skillQuery) {
@@ -520,7 +565,7 @@ export class SpecsfyTui {
520
565
  preview.setContent(item
521
566
  ? renderMarkdown(formatBacklogPreview(item.content))
522
567
  : "Crie arquivos em specs/backlog/<NNNN>-<slug>.md para visualizá-los aqui.");
523
- this.#screen?.render();
568
+ this.renderScreen();
524
569
  };
525
570
  list.on("select item", updatePreview);
526
571
  list.on("keypress", updatePreview);
@@ -614,7 +659,9 @@ export class SpecsfyTui {
614
659
  border: { fg: TUI_THEME.focusBackground },
615
660
  },
616
661
  });
662
+ this.#modalFocus = screen.focused;
617
663
  this.#modal = modal;
664
+ screen.focusPush(modal);
618
665
  modal.focus();
619
666
  screen.render();
620
667
  }
@@ -677,7 +724,7 @@ export class SpecsfyTui {
677
724
  this.showTab("tests");
678
725
  });
679
726
  if (this.#testResultTab === "summary") {
680
- blessed.box({
727
+ this.#testSummaryPanel = blessed.box({
681
728
  parent: body,
682
729
  top: 6,
683
730
  left: 1,
@@ -698,7 +745,7 @@ export class SpecsfyTui {
698
745
  // O blessed.log agenda a rolagem depois de setContent e acessa widgets
699
746
  // destruídos quando a saída ao vivo recria a aba. A caixa preserva a
700
747
  // rolagem sem deixar callbacks pendentes entre duas renderizações.
701
- blessed.box({
748
+ this.#testOutputPanel = blessed.box({
702
749
  parent: body,
703
750
  top: 6,
704
751
  left: 1,
@@ -720,9 +767,9 @@ export class SpecsfyTui {
720
767
  button.focus();
721
768
  }
722
769
  async runTests() {
723
- this.showTab("tests");
724
- if (this.#testRunning)
770
+ if (this.#modal || this.#testRunning)
725
771
  return;
772
+ this.showTab("tests");
726
773
  this.#testRunning = true;
727
774
  this.#testResultTab = "output";
728
775
  this.#testOutput = [];
@@ -732,27 +779,32 @@ export class SpecsfyTui {
732
779
  try {
733
780
  const result = await runProjectTests(this.project, (line) => {
734
781
  this.#testOutput.push(line);
782
+ this.#testOutputPanel?.setContent(this.#testOutput.join("\n"));
735
783
  if (this.#activeTab === "tests" && this.#testResultTab === "output") {
736
- this.showTab("tests");
784
+ this.renderScreen();
737
785
  }
738
786
  });
739
787
  this.#testSummary = formatTestRun(result);
788
+ this.#testSummaryPanel?.setContent(this.#testSummary);
740
789
  this.#testResultTab = "summary";
741
790
  this.showStatus(`${result.exit_code === 0 ? "Testes passaram" : "Testes falharam"} · ` +
742
791
  `${result.duration_seconds.toFixed(2)}s · exit code ${result.exit_code}`);
743
- this.showTab("tests");
792
+ if (this.#activeTab === "tests")
793
+ this.showTab("tests");
744
794
  }
745
795
  catch (error) {
746
796
  const message = `Erro ao executar testes: ${errorMessage(error)}`;
747
797
  this.#testSummary = message;
798
+ this.#testSummaryPanel?.setContent(message);
748
799
  this.#testOutput.push(message);
749
800
  this.#testResultTab = "summary";
750
801
  this.showStatus(message);
751
- this.showTab("tests");
802
+ if (this.#activeTab === "tests")
803
+ this.showTab("tests");
752
804
  }
753
805
  finally {
754
806
  this.#testRunning = false;
755
- this.#screen?.render();
807
+ this.renderScreen();
756
808
  }
757
809
  }
758
810
  renderSkills(body) {
@@ -904,7 +956,7 @@ export class SpecsfyTui {
904
956
  detail.setContent(option
905
957
  ? formatSkillDetail(option, this.#installed, this.#selected, this.#detected)
906
958
  : "Nenhuma skill visível.\n\nAjuste a busca ou os filtros para continuar.");
907
- this.#screen?.render();
959
+ this.renderScreen();
908
960
  };
909
961
  list.on("select item", updateDetail);
910
962
  list.on("keypress", () => setImmediate(updateDetail));
@@ -956,7 +1008,18 @@ export class SpecsfyTui {
956
1008
  "Ctrl+Q encerra a interface.",
957
1009
  });
958
1010
  }
959
- async refreshAll(reloadCatalog) {
1011
+ refreshAll(reloadCatalog) {
1012
+ if (this.#refreshPromise)
1013
+ return this.#refreshPromise;
1014
+ const promise = this.refreshAllInternal(reloadCatalog);
1015
+ const tracked = promise.finally(() => {
1016
+ if (this.#refreshPromise === tracked)
1017
+ this.#refreshPromise = undefined;
1018
+ });
1019
+ this.#refreshPromise = tracked;
1020
+ return tracked;
1021
+ }
1022
+ async refreshAllInternal(reloadCatalog) {
960
1023
  try {
961
1024
  const [specs, backlogs, lock] = await Promise.all([
962
1025
  scanSpecs(this.project),
@@ -965,8 +1028,8 @@ export class SpecsfyTui {
965
1028
  ]);
966
1029
  this.#specs = specs;
967
1030
  this.#backlogs = backlogs;
968
- this.#installed = new Set(Object.keys(lock.skills).filter(isSpecsfySkill));
969
- this.#selected = new Set(this.#installed);
1031
+ const installed = new Set(Object.keys(lock.skills).filter(isSpecsfySkill));
1032
+ this.#installed = installed;
970
1033
  if (reloadCatalog || !this.#catalog) {
971
1034
  try {
972
1035
  this.#catalog = await Catalog.fetch();
@@ -974,10 +1037,14 @@ export class SpecsfyTui {
974
1037
  this.#catalogError = "";
975
1038
  }
976
1039
  catch (error) {
977
- this.#catalogEntries = [];
978
1040
  this.#catalogError = errorMessage(error);
979
1041
  }
980
1042
  }
1043
+ const available = new Set(buildSkillOptions(this.#catalogEntries, installed).map((option) => option.name));
1044
+ this.#selected = this.#loaded && this.#selectionDirty
1045
+ ? new Set([...this.#selected].filter((name) => available.has(name)))
1046
+ : new Set(installed);
1047
+ this.#loaded = true;
981
1048
  this.#fingerprints = {
982
1049
  specs: await specsFingerprint(this.project),
983
1050
  backlogs: await backlogsFingerprint(this.project),
@@ -1019,20 +1086,26 @@ export class SpecsfyTui {
1019
1086
  }
1020
1087
  selectFramework() {
1021
1088
  this.#selected = new Set([...this.#selected, ...FRAMEWORK_SKILLS]);
1089
+ this.#selectionDirty = true;
1022
1090
  this.showStatus("As skills do framework foram selecionadas. Use ^A para aplicar.");
1023
1091
  this.showTab("skills");
1024
1092
  }
1025
1093
  async detectSkills() {
1094
+ if (this.#modal)
1095
+ return;
1026
1096
  this.showTab("skills");
1097
+ const startedOnSkills = this.#activeTab === "skills";
1027
1098
  try {
1028
1099
  this.#catalog ??= await Catalog.fetch();
1029
1100
  this.#catalogEntries = this.#catalog.entries;
1030
1101
  const entries = await this.#catalog.detect(this.project);
1031
1102
  this.#detected = new Set(entries.map((entry) => entry.name));
1032
1103
  this.#selected = new Set([...this.#selected, ...this.#detected]);
1104
+ this.#selectionDirty = true;
1033
1105
  this.#skillFilter = "detected";
1034
1106
  this.showStatus(`Skills detectadas e selecionadas: ${[...this.#detected].sort().join(", ") || "nenhuma skill"}`);
1035
- this.showTab("skills");
1107
+ if (startedOnSkills && this.#activeTab === "skills")
1108
+ this.showTab("skills");
1036
1109
  }
1037
1110
  catch (error) {
1038
1111
  this.showStatus(`Erro: ${errorMessage(error)}`);
@@ -1049,21 +1122,26 @@ export class SpecsfyTui {
1049
1122
  else {
1050
1123
  this.#selected.add(this.#selectedSkill);
1051
1124
  }
1125
+ this.#selectionDirty = true;
1052
1126
  this.showTab("skills");
1053
1127
  }
1054
1128
  selectVisible() {
1055
1129
  this.#suppressSkillToggleUntil = Date.now() + 100;
1056
1130
  for (const option of this.visibleSkills())
1057
1131
  this.#selected.add(option.name);
1132
+ this.#selectionDirty = true;
1058
1133
  this.showTab("skills");
1059
1134
  }
1060
1135
  clearVisible() {
1061
1136
  this.#suppressSkillToggleUntil = Date.now() + 100;
1062
1137
  for (const option of this.visibleSkills())
1063
1138
  this.#selected.delete(option.name);
1139
+ this.#selectionDirty = true;
1064
1140
  this.showTab("skills");
1065
1141
  }
1066
1142
  async applySkills() {
1143
+ if (this.#modal)
1144
+ return;
1067
1145
  this.showTab("skills");
1068
1146
  try {
1069
1147
  const selected = new Set([...this.#selected].filter(isSpecsfySkill));
@@ -1093,6 +1171,7 @@ export class SpecsfyTui {
1093
1171
  }
1094
1172
  if (toRemove.length)
1095
1173
  changed.push(...(await installer.remove(toRemove.sort())));
1174
+ this.#selectionDirty = false;
1096
1175
  await this.refreshAll(false);
1097
1176
  this.showStatus(changed.length
1098
1177
  ? `Seleção aplicada: ${changed.length} alteração(ões).`
@@ -1103,6 +1182,8 @@ export class SpecsfyTui {
1103
1182
  }
1104
1183
  }
1105
1184
  async updateSkills() {
1185
+ if (this.#modal)
1186
+ return;
1106
1187
  this.showTab("skills");
1107
1188
  try {
1108
1189
  const changed = await (await SkillInstaller.create(this.project)).updateAll();
@@ -1117,7 +1198,7 @@ export class SpecsfyTui {
1117
1198
  }
1118
1199
  showStatus(message) {
1119
1200
  this.#status?.setContent(message);
1120
- this.#screen?.render();
1201
+ this.renderScreen();
1121
1202
  }
1122
1203
  }
1123
1204
  /** Cria as opções base, especialistas e instalações fora do catálogo atual. */
@@ -1299,4 +1380,8 @@ function terminalScrollbar() {
1299
1380
  style: { bg: TUI_THEME.focusBackground },
1300
1381
  };
1301
1382
  }
1383
+ /** Detecta o estado encerrado sem depender da tipagem incompleta do blessed. */
1384
+ function screenDestroyed(screen) {
1385
+ return Boolean(screen.destroyed);
1386
+ }
1302
1387
  //# sourceMappingURL=tui.js.map