redmine-context 1.0.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.
Files changed (229) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +449 -0
  3. package/dist/bundle/index.d.ts +5 -0
  4. package/dist/bundle/index.js +5 -0
  5. package/dist/bundle/json.d.ts +90 -0
  6. package/dist/bundle/json.js +266 -0
  7. package/dist/bundle/markdown.d.ts +75 -0
  8. package/dist/bundle/markdown.js +294 -0
  9. package/dist/bundle/search-list.d.ts +43 -0
  10. package/dist/bundle/search-list.js +53 -0
  11. package/dist/bundle/stable-stringify.d.ts +26 -0
  12. package/dist/bundle/stable-stringify.js +50 -0
  13. package/dist/cache/contract.d.ts +157 -0
  14. package/dist/cache/contract.js +0 -0
  15. package/dist/cache/disk-index.d.ts +82 -0
  16. package/dist/cache/disk-index.js +220 -0
  17. package/dist/cache/disk.d.ts +133 -0
  18. package/dist/cache/disk.js +313 -0
  19. package/dist/cache/gc.d.ts +78 -0
  20. package/dist/cache/gc.js +123 -0
  21. package/dist/cache/get-or-compute.d.ts +36 -0
  22. package/dist/cache/get-or-compute.js +52 -0
  23. package/dist/cache/index.d.ts +9 -0
  24. package/dist/cache/index.js +8 -0
  25. package/dist/cache/keys.d.ts +76 -0
  26. package/dist/cache/keys.js +78 -0
  27. package/dist/cache/memory.d.ts +48 -0
  28. package/dist/cache/memory.js +110 -0
  29. package/dist/cache-first.d.ts +127 -0
  30. package/dist/cache-first.js +227 -0
  31. package/dist/client/errors.d.ts +33 -0
  32. package/dist/client/errors.js +49 -0
  33. package/dist/client/http.d.ts +110 -0
  34. package/dist/client/http.js +207 -0
  35. package/dist/client/index.d.ts +5 -0
  36. package/dist/client/index.js +5 -0
  37. package/dist/client/issues.d.ts +71 -0
  38. package/dist/client/issues.js +100 -0
  39. package/dist/client/search.d.ts +58 -0
  40. package/dist/client/search.js +81 -0
  41. package/dist/config/credentials.d.ts +247 -0
  42. package/dist/config/credentials.js +427 -0
  43. package/dist/config/doctor.d.ts +123 -0
  44. package/dist/config/doctor.js +260 -0
  45. package/dist/config/index.d.ts +6 -0
  46. package/dist/config/index.js +6 -0
  47. package/dist/config/keyring.d.ts +96 -0
  48. package/dist/config/keyring.js +158 -0
  49. package/dist/config/login.d.ts +97 -0
  50. package/dist/config/login.js +189 -0
  51. package/dist/config/settings.d.ts +94 -0
  52. package/dist/config/settings.js +140 -0
  53. package/dist/contract.d.ts +173 -0
  54. package/dist/contract.js +27 -0
  55. package/dist/core.d.ts +1 -0
  56. package/dist/core.js +8 -0
  57. package/dist/extract/audio-extractor.d.ts +105 -0
  58. package/dist/extract/audio-extractor.js +156 -0
  59. package/dist/extract/audio.d.ts +126 -0
  60. package/dist/extract/audio.js +184 -0
  61. package/dist/extract/dispatcher.d.ts +132 -0
  62. package/dist/extract/dispatcher.js +115 -0
  63. package/dist/extract/download.d.ts +111 -0
  64. package/dist/extract/download.js +261 -0
  65. package/dist/extract/duration.d.ts +106 -0
  66. package/dist/extract/duration.js +148 -0
  67. package/dist/extract/ffmpeg.d.ts +56 -0
  68. package/dist/extract/ffmpeg.js +95 -0
  69. package/dist/extract/gguf.d.ts +137 -0
  70. package/dist/extract/gguf.js +215 -0
  71. package/dist/extract/index.d.ts +19 -0
  72. package/dist/extract/index.js +19 -0
  73. package/dist/extract/magic.d.ts +80 -0
  74. package/dist/extract/magic.js +282 -0
  75. package/dist/extract/ooxml.d.ts +131 -0
  76. package/dist/extract/ooxml.js +336 -0
  77. package/dist/extract/pdf.d.ts +147 -0
  78. package/dist/extract/pdf.js +322 -0
  79. package/dist/extract/queue.d.ts +167 -0
  80. package/dist/extract/queue.js +217 -0
  81. package/dist/extract/subprocess.d.ts +145 -0
  82. package/dist/extract/subprocess.js +181 -0
  83. package/dist/extract/tesseract.d.ts +153 -0
  84. package/dist/extract/tesseract.js +321 -0
  85. package/dist/extract/video-extractor.d.ts +84 -0
  86. package/dist/extract/video-extractor.js +89 -0
  87. package/dist/extract/video.d.ts +198 -0
  88. package/dist/extract/video.js +418 -0
  89. package/dist/extract/which.d.ts +63 -0
  90. package/dist/extract/which.js +72 -0
  91. package/dist/extract/whisper-extract.d.ts +211 -0
  92. package/dist/extract/whisper-extract.js +323 -0
  93. package/dist/extract/whisper.d.ts +50 -0
  94. package/dist/extract/whisper.js +67 -0
  95. package/dist/extract/zip.d.ts +50 -0
  96. package/dist/extract/zip.js +165 -0
  97. package/dist/extract-issue-attachments.d.ts +82 -0
  98. package/dist/extract-issue-attachments.js +156 -0
  99. package/dist/fetch-attachment-text.d.ts +113 -0
  100. package/dist/fetch-attachment-text.js +155 -0
  101. package/dist/fetch-issue-bundle.d.ts +81 -0
  102. package/dist/fetch-issue-bundle.js +93 -0
  103. package/dist/fetch-issue-search.d.ts +74 -0
  104. package/dist/fetch-issue-search.js +120 -0
  105. package/dist/index.d.ts +21 -0
  106. package/dist/index.js +67 -0
  107. package/dist/normalize/collections.d.ts +52 -0
  108. package/dist/normalize/collections.js +170 -0
  109. package/dist/normalize/helpers.d.ts +35 -0
  110. package/dist/normalize/helpers.js +59 -0
  111. package/dist/normalize/index.d.ts +2 -0
  112. package/dist/normalize/index.js +2 -0
  113. package/dist/normalize/issue.d.ts +34 -0
  114. package/dist/normalize/issue.js +154 -0
  115. package/dist/surfaces/cli/commands.d.ts +61 -0
  116. package/dist/surfaces/cli/commands.js +262 -0
  117. package/dist/surfaces/cli/main.d.ts +32 -0
  118. package/dist/surfaces/cli/main.js +210 -0
  119. package/dist/surfaces/cli/prompts.d.ts +66 -0
  120. package/dist/surfaces/cli/prompts.js +147 -0
  121. package/dist/surfaces/cli/tty.d.ts +27 -0
  122. package/dist/surfaces/cli/tty.js +35 -0
  123. package/dist/surfaces/cli/types.d.ts +39 -0
  124. package/dist/surfaces/cli/types.js +7 -0
  125. package/dist/surfaces/mcp/server.d.ts +171 -0
  126. package/dist/surfaces/mcp/server.js +427 -0
  127. package/dist/surfaces/tui/app.d.ts +55 -0
  128. package/dist/surfaces/tui/app.js +180 -0
  129. package/dist/surfaces/tui/attachment-status.d.ts +79 -0
  130. package/dist/surfaces/tui/attachment-status.js +113 -0
  131. package/dist/surfaces/tui/components/breadcrumb.d.ts +7 -0
  132. package/dist/surfaces/tui/components/breadcrumb.js +31 -0
  133. package/dist/surfaces/tui/components/gradient-text.d.ts +23 -0
  134. package/dist/surfaces/tui/components/gradient-text.js +75 -0
  135. package/dist/surfaces/tui/components/scroll-view.d.ts +25 -0
  136. package/dist/surfaces/tui/components/scroll-view.js +77 -0
  137. package/dist/surfaces/tui/components/spinner.d.ts +12 -0
  138. package/dist/surfaces/tui/components/spinner.js +39 -0
  139. package/dist/surfaces/tui/components/text-input.d.ts +45 -0
  140. package/dist/surfaces/tui/components/text-input.js +114 -0
  141. package/dist/surfaces/tui/format-file-size.d.ts +24 -0
  142. package/dist/surfaces/tui/format-file-size.js +43 -0
  143. package/dist/surfaces/tui/glyphs.d.ts +47 -0
  144. package/dist/surfaces/tui/glyphs.js +84 -0
  145. package/dist/surfaces/tui/hooks/use-auth-guard.d.ts +39 -0
  146. package/dist/surfaces/tui/hooks/use-auth-guard.js +135 -0
  147. package/dist/surfaces/tui/hooks/use-doctor-status.d.ts +64 -0
  148. package/dist/surfaces/tui/hooks/use-doctor-status.js +123 -0
  149. package/dist/surfaces/tui/hooks/use-escape-interceptor.d.ts +25 -0
  150. package/dist/surfaces/tui/hooks/use-escape-interceptor.js +65 -0
  151. package/dist/surfaces/tui/hooks/use-exit-guard.d.ts +18 -0
  152. package/dist/surfaces/tui/hooks/use-exit-guard.js +66 -0
  153. package/dist/surfaces/tui/hooks/use-export-bundle.d.ts +62 -0
  154. package/dist/surfaces/tui/hooks/use-export-bundle.js +100 -0
  155. package/dist/surfaces/tui/hooks/use-issue-detail.d.ts +61 -0
  156. package/dist/surfaces/tui/hooks/use-issue-detail.js +132 -0
  157. package/dist/surfaces/tui/hooks/use-issue-search.d.ts +71 -0
  158. package/dist/surfaces/tui/hooks/use-issue-search.js +168 -0
  159. package/dist/surfaces/tui/hooks/use-list-navigation.d.ts +44 -0
  160. package/dist/surfaces/tui/hooks/use-list-navigation.js +82 -0
  161. package/dist/surfaces/tui/hooks/use-media-binaries.d.ts +24 -0
  162. package/dist/surfaces/tui/hooks/use-media-binaries.js +44 -0
  163. package/dist/surfaces/tui/hooks/use-my-issues.d.ts +66 -0
  164. package/dist/surfaces/tui/hooks/use-my-issues.js +151 -0
  165. package/dist/surfaces/tui/hooks/use-onboarding-callbacks.d.ts +11 -0
  166. package/dist/surfaces/tui/hooks/use-onboarding-callbacks.js +104 -0
  167. package/dist/surfaces/tui/hooks/use-terminal-width.d.ts +52 -0
  168. package/dist/surfaces/tui/hooks/use-terminal-width.js +90 -0
  169. package/dist/surfaces/tui/index.d.ts +50 -0
  170. package/dist/surfaces/tui/index.js +158 -0
  171. package/dist/surfaces/tui/instance.d.ts +37 -0
  172. package/dist/surfaces/tui/instance.js +36 -0
  173. package/dist/surfaces/tui/job-registry.d.ts +113 -0
  174. package/dist/surfaces/tui/job-registry.js +123 -0
  175. package/dist/surfaces/tui/job-status.d.ts +45 -0
  176. package/dist/surfaces/tui/job-status.js +81 -0
  177. package/dist/surfaces/tui/navigation.d.ts +74 -0
  178. package/dist/surfaces/tui/navigation.js +87 -0
  179. package/dist/surfaces/tui/palettes.d.ts +38 -0
  180. package/dist/surfaces/tui/palettes.js +244 -0
  181. package/dist/surfaces/tui/screen.d.ts +30 -0
  182. package/dist/surfaces/tui/screen.js +51 -0
  183. package/dist/surfaces/tui/screens/about.d.ts +2 -0
  184. package/dist/surfaces/tui/screens/about.js +35 -0
  185. package/dist/surfaces/tui/screens/appearance.d.ts +2 -0
  186. package/dist/surfaces/tui/screens/appearance.js +74 -0
  187. package/dist/surfaces/tui/screens/config.d.ts +2 -0
  188. package/dist/surfaces/tui/screens/config.js +82 -0
  189. package/dist/surfaces/tui/screens/doctor.d.ts +2 -0
  190. package/dist/surfaces/tui/screens/doctor.js +109 -0
  191. package/dist/surfaces/tui/screens/export.d.ts +2 -0
  192. package/dist/surfaces/tui/screens/export.js +168 -0
  193. package/dist/surfaces/tui/screens/home-selection.d.ts +70 -0
  194. package/dist/surfaces/tui/screens/home-selection.js +80 -0
  195. package/dist/surfaces/tui/screens/home.d.ts +2 -0
  196. package/dist/surfaces/tui/screens/home.js +200 -0
  197. package/dist/surfaces/tui/screens/issue-detail.d.ts +6 -0
  198. package/dist/surfaces/tui/screens/issue-detail.js +182 -0
  199. package/dist/surfaces/tui/screens/jobs.d.ts +7 -0
  200. package/dist/surfaces/tui/screens/jobs.js +89 -0
  201. package/dist/surfaces/tui/screens/loaded-issue-context.d.ts +49 -0
  202. package/dist/surfaces/tui/screens/loaded-issue-context.js +57 -0
  203. package/dist/surfaces/tui/screens/onboarding/api-key.d.ts +2 -0
  204. package/dist/surfaces/tui/screens/onboarding/api-key.js +69 -0
  205. package/dist/surfaces/tui/screens/onboarding/login.d.ts +2 -0
  206. package/dist/surfaces/tui/screens/onboarding/login.js +50 -0
  207. package/dist/surfaces/tui/screens/onboarding/mode.d.ts +2 -0
  208. package/dist/surfaces/tui/screens/onboarding/mode.js +42 -0
  209. package/dist/surfaces/tui/screens/onboarding/onboarding-context.d.ts +221 -0
  210. package/dist/surfaces/tui/screens/onboarding/onboarding-context.js +131 -0
  211. package/dist/surfaces/tui/screens/onboarding/success.d.ts +2 -0
  212. package/dist/surfaces/tui/screens/onboarding/success.js +41 -0
  213. package/dist/surfaces/tui/screens/onboarding/url.d.ts +24 -0
  214. package/dist/surfaces/tui/screens/onboarding/url.js +84 -0
  215. package/dist/surfaces/tui/screens/onboarding/validating.d.ts +1 -0
  216. package/dist/surfaces/tui/screens/onboarding/validating.js +86 -0
  217. package/dist/surfaces/tui/screens/welcome.d.ts +6 -0
  218. package/dist/surfaces/tui/screens/welcome.js +95 -0
  219. package/dist/surfaces/tui/status-color.d.ts +21 -0
  220. package/dist/surfaces/tui/status-color.js +23 -0
  221. package/dist/surfaces/tui/symbols.d.ts +231 -0
  222. package/dist/surfaces/tui/symbols.js +14 -0
  223. package/dist/surfaces/tui/terminal-colors.d.ts +29 -0
  224. package/dist/surfaces/tui/terminal-colors.js +39 -0
  225. package/dist/surfaces/tui/theme.d.ts +156 -0
  226. package/dist/surfaces/tui/theme.js +86 -0
  227. package/dist/surfaces/tui/truncate.d.ts +31 -0
  228. package/dist/surfaces/tui/truncate.js +81 -0
  229. package/package.json +93 -0
@@ -0,0 +1,182 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * Tela de detalhe de issue (#31) — substitui o placeholder mínimo da #29.
4
+ *
5
+ * Carrega a issue completa via `../hooks/use-issue-detail.js`
6
+ * (`getIssue`+`normalizeIssue` do core, DIRETO — sem passar pelo bundle
7
+ * Markdown/JSON, que é o formato de saída da CLI/MCP, não o modelo em
8
+ * memória que esta tela precisa para paginar/rolar o conteúdo). A issue a
9
+ * buscar vem de `./home-selection.js` (`selectedIssueId`, escrito pela home
10
+ * ao empilhar esta tela — ver o JSDoc daquele módulo para o porquê de um
11
+ * contexto compartilhado em vez de props).
12
+ *
13
+ * Layout: metadados (status/prioridade/autor/responsável/datas) FIXOS no
14
+ * topo — nunca rolam — e descrição + histórico de journals dentro de um
15
+ * `../components/scroll-view.tsx` (viewport de altura fixa,
16
+ * `j`/`k`/setas/PgUp/PgDn), para que a tela não quebre o layout mesmo com uma
17
+ * issue com dezenas de journals.
18
+ *
19
+ * `Esc` (atalho global, `../app.tsx`) desempilha normalmente — a home é
20
+ * remontada, mas `selectedIndex` é preservado via `./home-selection.js`
21
+ * (documentado ali). `b` é o atalho local, mesmo padrão de
22
+ * `doctor.tsx`/`config.tsx`.
23
+ *
24
+ * 401 é responsabilidade de `useAuthGuard`, já embutido em
25
+ * `use-issue-detail.js` (mandatório, M2-13/#36) — esta tela só formata o
26
+ * estado `auth-aborted` (neutro) igual aos demais banners de erro.
27
+ *
28
+ * Anexos (#32): seção "Anexos" ao FIM do mesmo `ScrollView` — nome, tamanho
29
+ * humanizado (`../format-file-size.js`) e badge de status
30
+ * (`../attachment-status.js`); mesma rolagem, sem bloquear navegação.
31
+ *
32
+ * `e` (issue carregada) empilha a tela de exportação (#33, `./export.js`):
33
+ * a issue normalizada é espelhada em `./loaded-issue-context.js` para a
34
+ * exportação REUSAR a issue em memória (ver JSDoc daquele módulo).
35
+ */
36
+ import { Box, Text, useInput } from 'ink';
37
+ import { useEffect, useRef } from 'react';
38
+ import { attachmentStatusColor, attachmentStatusLabel, deriveAttachmentExtractionStatus, } from '../attachment-status.js';
39
+ import { Spinner } from '../components/spinner.js';
40
+ import { ScrollView } from '../components/scroll-view.js';
41
+ import { glyphs } from '../glyphs.js';
42
+ import { humanizeFileSize } from '../format-file-size.js';
43
+ import { useIssueDetail } from '../hooks/use-issue-detail.js';
44
+ import { useTerminalHeight } from '../hooks/use-terminal-width.js';
45
+ import { useNavigation } from '../navigation.js';
46
+ import { statusColor } from '../status-color.js';
47
+ import { symbols } from '../symbols.js';
48
+ import { useTheme } from '../theme.js';
49
+ import { useHomeSelection } from './home-selection.js';
50
+ import { useLoadedIssue } from './loaded-issue-context.js';
51
+ /**
52
+ * Overhead de linhas FORA da viewport de descrição (breadcrumb + título + meta +
53
+ * rodapé + paddings). A viewport cresce com o terminal: `altura - OVERHEAD`
54
+ * (#190). Com a altura default dos testes (24) dá 10 — o valor histórico — então
55
+ * os snapshots/asserções de rolagem seguem iguais; em terminal real a descrição
56
+ * usa quase toda a tela e só rola quando passa DE VERDADE.
57
+ */
58
+ const CONTENT_OVERHEAD_ROWS = 14;
59
+ /** Piso da viewport (terminais muito baixos). */
60
+ const CONTENT_MIN_HEIGHT = 6;
61
+ /** Placeholder discreto para campos ausentes (assignee, autor/data de journal, old/new value). ASCII no Windows legado (#84). */
62
+ const EMPTY_PLACEHOLDER = glyphs.emptyPlaceholder;
63
+ /** Metadados fixos no topo: id/subject, status/prioridade, autor/responsável, datas. */
64
+ function IssueMeta({ issue, theme }) {
65
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.primary, children: ["#", issue.id, " ", issue.subject] }), _jsxs(Box, { children: [_jsxs(Text, { color: statusColor(theme, issue.status.name), children: ["[", issue.status.name, "]"] }), _jsx(Text, { children: " " }), _jsx(Text, { color: theme.accent, children: issue.priority.name })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "Autor: " }), _jsx(Text, { children: issue.author.name }), _jsx(Text, { color: theme.muted, children: " Respons\u00E1vel: " }), _jsx(Text, { children: issue.assigned_to?.name ?? EMPTY_PLACEHOLDER })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "Criada em: " }), _jsx(Text, { children: issue.created_on }), _jsx(Text, { color: theme.muted, children: " Atualizada em: " }), _jsx(Text, { children: issue.updated_on })] })] }));
66
+ }
67
+ /** Constrói a linha de um único anexo: nome (texto derivado, puro) · tamanho humanizado · content_type · badge de status. */
68
+ function buildAttachmentRow(attachment, theme) {
69
+ const status = deriveAttachmentExtractionStatus(attachment);
70
+ return (_jsxs(Text, { children: [attachment.filename, ' ', _jsxs(Text, { color: theme.muted, children: ["(", humanizeFileSize(attachment.filesize), " ", glyphs.middleDot, " ", attachment.content_type ?? EMPTY_PLACEHOLDER, ")"] }), ' ', _jsxs(Text, { color: attachmentStatusColor(theme, status), children: ["[", attachmentStatusLabel(status), "]"] })] }, `attachment-${attachment.id}`));
71
+ }
72
+ /** Constrói as linhas da seção de anexos (após os journals): cabeçalho + 1 linha por anexo, ou "(nenhum)". */
73
+ function buildAttachmentRows(issue, theme) {
74
+ const rows = [
75
+ _jsx(Text, { color: theme.primary, children: "Anexos" }, "attachments-header"),
76
+ ];
77
+ if (issue.attachments.length === 0) {
78
+ rows.push(_jsx(Text, { color: theme.muted, children: "(nenhum)" }, "attachments-empty"));
79
+ }
80
+ else {
81
+ issue.attachments.forEach((attachment) => {
82
+ rows.push(buildAttachmentRow(attachment, theme));
83
+ });
84
+ }
85
+ return rows;
86
+ }
87
+ /** Constrói as linhas da viewport rolável: bloco de descrição + histórico cronológico de journals. */
88
+ function buildContentRows(issue, theme) {
89
+ const rows = [];
90
+ rows.push(_jsx(Text, { color: theme.primary, children: "Descri\u00E7\u00E3o" }, "desc-header"));
91
+ if (issue.description === undefined || issue.description === '') {
92
+ rows.push(_jsx(Text, { color: theme.muted, children: "(sem descri\u00E7\u00E3o)" }, "desc-empty"));
93
+ }
94
+ else {
95
+ issue.description.split('\n').forEach((line, index) => {
96
+ rows.push(_jsx(Text, { children: line.length > 0 ? line : ' ' }, `desc-${index}`));
97
+ });
98
+ }
99
+ rows.push(_jsx(Text, { children: " " }, "desc-spacer"));
100
+ rows.push(_jsx(Text, { color: theme.primary, children: "Hist\u00F3rico" }, "journals-header"));
101
+ if (issue.journals.length === 0) {
102
+ rows.push(_jsx(Text, { color: theme.muted, children: "(nenhuma atualiza\u00E7\u00E3o registrada)" }, "journals-empty"));
103
+ }
104
+ else {
105
+ // Cronológico: preserva a ordem já entregue por `getIssue`/`normalizeIssue`.
106
+ issue.journals.forEach((journal) => {
107
+ rows.push(_jsxs(Text, { color: theme.muted, children: [journal.user?.name ?? EMPTY_PLACEHOLDER, " ", glyphs.middleDot, " ", journal.created_on] }, `journal-${journal.id}-head`));
108
+ if (journal.notes !== undefined && journal.notes !== '') {
109
+ journal.notes.split('\n').forEach((line, index) => {
110
+ rows.push(_jsx(Text, { children: line }, `journal-${journal.id}-note-${index}`));
111
+ });
112
+ }
113
+ // Details estruturais resumidos: `campo: antigo → novo`, sempre em muted.
114
+ journal.details.forEach((detail, index) => {
115
+ rows.push(_jsxs(Text, { color: theme.muted, children: [' ', detail.name, ": ", detail.old_value ?? EMPTY_PLACEHOLDER, " ", symbols.arrowRight, ' ', detail.new_value ?? EMPTY_PLACEHOLDER] }, `journal-${journal.id}-detail-${index}`));
116
+ });
117
+ rows.push(_jsx(Text, { children: " " }, `journal-${journal.id}-spacer`));
118
+ });
119
+ }
120
+ rows.push(_jsx(Text, { children: " " }, "journals-attachments-spacer"));
121
+ // Anexos (#32) SEMPRE por último — após journals, dentro do mesmo ScrollView.
122
+ rows.push(...buildAttachmentRows(issue, theme));
123
+ return rows;
124
+ }
125
+ /** Rodapé com os atalhos disponíveis nesta tela. */
126
+ function Footer({ showRetry, showExport }) {
127
+ const theme = useTheme();
128
+ return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: [showRetry ? (_jsxs(_Fragment, { children: ["Pressione", ' ', _jsx(Text, { color: theme.accent, children: "r" }), ' ', "para tentar de novo,", ' '] })) : null, showExport ? (_jsxs(_Fragment, { children: ["Pressione", ' ', _jsx(Text, { color: theme.accent, children: "e" }), ' ', "para exportar,", ' '] })) : null, "Pressione", ' ', _jsx(Text, { color: theme.accent, children: "t" }), ' ', "para ver os jobs,", ' ', _jsx(Text, { color: theme.accent, children: "b" }), ' ', "para voltar."] }) }));
129
+ }
130
+ /**
131
+ * Tela de detalhe: metadados fixos + descrição/journals em viewport rolável.
132
+ * `b` (ou `Esc`, atalho global) volta para a home — `selectedIndex` da lista
133
+ * é preservado via `./home-selection.js`.
134
+ */
135
+ export function IssueDetailScreen() {
136
+ const { pop, push } = useNavigation();
137
+ const theme = useTheme();
138
+ const { selectedIssueId } = useHomeSelection();
139
+ const { state, retry } = useIssueDetail(selectedIssueId);
140
+ const { setIssue } = useLoadedIssue();
141
+ // Viewport de descrição CRESCE com o terminal (#190) — só rola quando o conteúdo
142
+ // passa da tela de verdade, em vez de uma janelinha fixa com espaço vazio embaixo.
143
+ const contentHeight = Math.max(CONTENT_MIN_HEIGHT, useTerminalHeight() - CONTENT_OVERHEAD_ROWS);
144
+ // Espelha a issue carregada no contexto leve consumido por `./export.js`
145
+ // (#33) — ver o JSDoc do módulo e de `./loaded-issue-context.js` para a
146
+ // escolha de reuso em memória em vez de refetch.
147
+ const setIssueRef = useRef(setIssue);
148
+ setIssueRef.current = setIssue;
149
+ useEffect(() => {
150
+ if (state.status === 'loaded') {
151
+ setIssueRef.current(state.issue);
152
+ }
153
+ }, [state]);
154
+ useInput((input) => {
155
+ if (input === 'b') {
156
+ pop();
157
+ return;
158
+ }
159
+ if (input === 'e' && state.status === 'loaded') {
160
+ push('export');
161
+ return;
162
+ }
163
+ // #34 (M2-11): "t" abre o painel de jobs da sessão (`./jobs.js`) — em
164
+ // qualquer estado (loading/erro/loaded), sem restrição, ao contrário de
165
+ // "e" (exportar), que só faz sentido com a issue já carregada.
166
+ if (input === 't') {
167
+ push('jobs');
168
+ return;
169
+ }
170
+ if (input === 'r' &&
171
+ (state.status === 'error-network' ||
172
+ state.status === 'error-forbidden' ||
173
+ state.status === 'error-not-found' ||
174
+ state.status === 'auth-aborted')) {
175
+ retry();
176
+ }
177
+ });
178
+ return (_jsxs(Box, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: theme.primary, children: "Detalhe da issue" }), state.status === 'no-selection' ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: "Nenhuma issue selecionada." }) })) : null, state.status === 'loading' ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Spinner, {}), " Carregando issue..."] }) })) : null, state.status === 'error-network' ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.danger, children: [symbols.cross, " Falha ao carregar a issue: ", state.message] }) })) : null, state.status === 'error-forbidden' ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.danger, children: [symbols.cross, " ", state.message] }) })) : null, state.status === 'error-not-found' ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.danger, children: [symbols.cross, " ", state.message] }) })) : null, state.status === 'auth-aborted' ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: state.message }) })) : null, state.status === 'loaded' ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(IssueMeta, { issue: state.issue, theme: theme }), _jsx(Box, { marginTop: 1, children: _jsx(ScrollView, { lines: buildContentRows(state.issue, theme), height: contentHeight }) })] })) : null, _jsx(Box, { flexGrow: 1 }), _jsx(Footer, { showRetry: state.status === 'error-network' ||
179
+ state.status === 'error-forbidden' ||
180
+ state.status === 'error-not-found' ||
181
+ state.status === 'auth-aborted', showExport: state.status === 'loaded' })] }));
182
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tela do painel de jobs: lista as operações da sessão (`../job-registry.tsx`),
3
+ * `↑/↓`/`j`/`k` navegam a seleção, `Ctrl+C` cancela o job selecionado (só se
4
+ * `cancelable`, ver o JSDoc de `../job-registry.tsx`), `b` (ou `Esc`, atalho
5
+ * global) volta.
6
+ */
7
+ export declare function JobsScreen(): import("react").JSX.Element;
@@ -0,0 +1,89 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Painel de jobs da sessão (#34/M2-11) — empilhado a partir da home
4
+ * (`./home.tsx`) ou do detalhe (`./issue-detail.tsx`) com a tecla `t`
5
+ * ("tarefas"), disponível em qualquer estado das duas telas.
6
+ *
7
+ * Lista as operações registradas em `../job-registry.tsx` (ver o JSDoc
8
+ * daquele módulo para o contrato completo) — hoje só a exportação de bundle
9
+ * (`./export.tsx`, #33) é um produtor real; a tela já está pronta para
10
+ * qualquer job futuro do M3/M4 sem mudança de contrato.
11
+ *
12
+ * ## `ink-task-list`: incompatibilidade verificada, componente próprio
13
+ *
14
+ * A AC pedia `ink-task-list` (já na dependência do projeto) OU um componente
15
+ * próprio "se a lib conflitar com ink@6". Testado manualmente antes de
16
+ * qualquer implementação (`ink-testing-library` + `ink-task-list@2.0.0`):
17
+ *
18
+ * 1. **Sem export default** — só exporta `{ Task, TaskList }` nomeados (o
19
+ * típico `import TaskList, { Task } from 'ink-task-list'` sugerido pela
20
+ * doc da lib quebra com `ink@6`/ESM estrito).
21
+ * 2. **Cores hardcoded internas** (amarelo/vermelho/verde/cinza fixos no
22
+ * próprio bundle da lib) — incompatível com a AC "cores do tema": a
23
+ * varredura anti-hardcode do repo (`tests/surfaces/tui/no-hardcoded-colors.test.ts`)
24
+ * só cobre `src/surfaces/tui`, então isso não quebraria o CI, mas o
25
+ * resultado visual NUNCA respeitaria um tema customizado — justamente o
26
+ * problema que `theme.ts` existe para resolver.
27
+ * 3. O estado `'loading'` da lib **exige** um prop `spinner` explícito
28
+ * (`{ frames, interval }`, sem default) — omiti-lo quebra o render com
29
+ * `Cannot read properties of undefined (reading 'frames')`. Contornável
30
+ * passando um objeto de frames à mão, mas é mais um sinal de uma lib
31
+ * parada na era do ink@3 (suas `devDependencies` fixam `ink@^3.2.0` e
32
+ * `react@^17.0.2`).
33
+ *
34
+ * Dado o conflito real com a AC de tema (item 2) e a fragilidade do item 3,
35
+ * a escolha foi um componente próprio, reaproveitando peças já existentes:
36
+ * `../symbols.ts` (ícones, com fallback ASCII automático) para os estados
37
+ * estáticos e o `Spinner` (`../components/spinner.tsx`) já usado por
38
+ * `./export.tsx`/`./home.tsx` para `processing` — ver `../job-status.ts` para
39
+ * o mapeamento ícone/cor por status.
40
+ */
41
+ import { Box, Text, useInput } from 'ink';
42
+ import { useRef } from 'react';
43
+ import { Spinner } from '../components/spinner.js';
44
+ import { glyphs } from '../glyphs.js';
45
+ import { useListNavigation } from '../hooks/use-list-navigation.js';
46
+ import { useJobRegistry } from '../job-registry.js';
47
+ import { jobStatusColor, jobStatusIcon, jobStatusLabel } from '../job-status.js';
48
+ import { useNavigation } from '../navigation.js';
49
+ import { symbols } from '../symbols.js';
50
+ import { useTheme } from '../theme.js';
51
+ /** Uma linha do painel: ícone/spinner de status, rótulo, badge textual e, quando selecionada, o hint de cancelamento. */
52
+ function JobRow({ job, selected, theme }) {
53
+ const icon = jobStatusIcon(job.status);
54
+ return (_jsxs(Box, { children: [_jsx(Text, { color: theme.primary, children: selected ? `${symbols.pointerSmall} ` : ' ' }), job.status === 'processing' ? (_jsx(Spinner, {})) : (_jsx(Text, { color: jobStatusColor(theme, job.status), children: icon })), _jsx(Text, { children: " " }), _jsx(Text, { ...(selected ? { color: theme.primary } : {}), children: job.label }), _jsxs(Text, { color: theme.muted, children: [" [", jobStatusLabel(job.status), "]"] }), selected ? (_jsxs(Text, { color: theme.muted, children: [' ', job.cancelable === true ? '(Ctrl+C cancela)' : '(não cancelável)'] })) : null] }));
55
+ }
56
+ /**
57
+ * Tela do painel de jobs: lista as operações da sessão (`../job-registry.tsx`),
58
+ * `↑/↓`/`j`/`k` navegam a seleção, `Ctrl+C` cancela o job selecionado (só se
59
+ * `cancelable`, ver o JSDoc de `../job-registry.tsx`), `b` (ou `Esc`, atalho
60
+ * global) volta.
61
+ */
62
+ export function JobsScreen() {
63
+ const theme = useTheme();
64
+ const { pop } = useNavigation();
65
+ const { jobs } = useJobRegistry();
66
+ const { selectedIndex } = useListNavigation(jobs.length);
67
+ // Handlers ESTÁVEIS (refs + useCallback via closures simples aqui, padrão
68
+ // do repo): identidade nova a cada render des/re-subscreve o useInput e
69
+ // pode perder uma tecla rápida entre pressões próximas.
70
+ const popRef = useRef(pop);
71
+ popRef.current = pop;
72
+ const jobsRef = useRef(jobs);
73
+ jobsRef.current = jobs;
74
+ const selectedIndexRef = useRef(selectedIndex);
75
+ selectedIndexRef.current = selectedIndex;
76
+ useInput((input, key) => {
77
+ if (input === 'b') {
78
+ popRef.current();
79
+ return;
80
+ }
81
+ if (key.ctrl && input === 'c') {
82
+ const selectedJob = jobsRef.current[selectedIndexRef.current];
83
+ if (selectedJob?.cancelable === true) {
84
+ selectedJob.onCancel?.();
85
+ }
86
+ }
87
+ });
88
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: theme.primary, children: "Jobs da sess\u00E3o" }), jobs.length === 0 ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: "Nenhum job nesta sess\u00E3o ainda." }) })) : (_jsx(Box, { marginTop: 1, flexDirection: "column", children: jobs.map((job, index) => (_jsx(JobRow, { job: job, selected: index === selectedIndex, theme: theme }, job.id))) })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: [_jsx(Text, { color: theme.accent, children: `${glyphs.arrowUp}/${glyphs.arrowDown}` }), ' ', "navega,", ' ', _jsx(Text, { color: theme.accent, children: "Ctrl+C" }), ' ', "cancela o job selecionado (se cancel\u00E1vel),", ' ', _jsx(Text, { color: theme.accent, children: "b" }), ' ', "volta."] }) })] }));
89
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Issue em memória compartilhada entre `./issue-detail.tsx` e `./export.tsx`
3
+ * (#33), mesmo padrão de `./home-selection.tsx` (contexto leve, instanciado
4
+ * uma vez em `../app.tsx`, sobrevivendo ao unmount de cada tela).
5
+ *
6
+ * ## Por que este contexto existe (escolha de reuso documentada, AC da #33)
7
+ *
8
+ * A tela de exportação precisa da MESMA {@link Issue} já carregada pelo
9
+ * detalhe para empacotar via `buildMarkdownBundle`/`buildJsonBundle`
10
+ * (`../../../index.js`). Duas opções foram consideradas:
11
+ *
12
+ * 1. **Reusar a issue em memória** (escolhida): `./issue-detail.tsx` grava a
13
+ * issue aqui assim que `useIssueDetail` resolve para `loaded`; `./export.tsx`
14
+ * lê daqui e chama os builders DIRETO — sem HTTP nem normalização de novo.
15
+ * 2. **Refetch simples**: `./export.tsx` chamaria `fetchIssueBundle` (ou o
16
+ * par `getIssue`+`normalizeIssue`) de novo, ignorando que o detalhe acabou
17
+ * de buscar a mesma issue segundos antes.
18
+ *
19
+ * (1) evita uma segunda chamada de rede redundante (a issue não muda entre o
20
+ * usuário abrir o detalhe e pressionar `e`) e é consistente com a decisão já
21
+ * tomada pela #31 de manter o modelo `Issue` em memória em vez de só um bundle
22
+ * serializado. O roteador (`../app.tsx`) só mantém montado o TOPO da pilha —
23
+ * ao empilhar `export` a partir do detalhe, `IssueDetailScreen` é desmontada,
24
+ * então a issue precisa sobreviver em algo ACIMA da pilha, daí o contexto
25
+ * (mesmo raciocínio do `HomeSelectionProvider`, ver o JSDoc daquele módulo).
26
+ */
27
+ import { type ReactNode } from 'react';
28
+ import type { Issue } from '../../../index.js';
29
+ /** Valor exposto pelo contexto da issue em memória. */
30
+ export interface LoadedIssueValue {
31
+ /** Última issue carregada por `./issue-detail.tsx`, ou `undefined` antes de qualquer carga. */
32
+ issue: Issue | undefined;
33
+ /** Registra a issue carregada — chamado pelo detalhe assim que `useIssueDetail` resolve para `loaded`. */
34
+ setIssue: (issue: Issue) => void;
35
+ }
36
+ /**
37
+ * Provider do contexto de issue em memória — instanciado uma vez em
38
+ * `../app.tsx`, envolvendo toda a árvore de telas (ver o JSDoc do módulo).
39
+ */
40
+ export declare function LoadedIssueProvider({ children }: {
41
+ children: ReactNode;
42
+ }): import("react").JSX.Element;
43
+ /**
44
+ * Hook de issue em memória consumido por `./issue-detail.tsx`/`./export.tsx`.
45
+ *
46
+ * @throws {Error} Se chamado fora de um `<LoadedIssueProvider>` — indica erro
47
+ * de composição (tela renderizada sem passar pelo roteador).
48
+ */
49
+ export declare function useLoadedIssue(): LoadedIssueValue;
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Issue em memória compartilhada entre `./issue-detail.tsx` e `./export.tsx`
4
+ * (#33), mesmo padrão de `./home-selection.tsx` (contexto leve, instanciado
5
+ * uma vez em `../app.tsx`, sobrevivendo ao unmount de cada tela).
6
+ *
7
+ * ## Por que este contexto existe (escolha de reuso documentada, AC da #33)
8
+ *
9
+ * A tela de exportação precisa da MESMA {@link Issue} já carregada pelo
10
+ * detalhe para empacotar via `buildMarkdownBundle`/`buildJsonBundle`
11
+ * (`../../../index.js`). Duas opções foram consideradas:
12
+ *
13
+ * 1. **Reusar a issue em memória** (escolhida): `./issue-detail.tsx` grava a
14
+ * issue aqui assim que `useIssueDetail` resolve para `loaded`; `./export.tsx`
15
+ * lê daqui e chama os builders DIRETO — sem HTTP nem normalização de novo.
16
+ * 2. **Refetch simples**: `./export.tsx` chamaria `fetchIssueBundle` (ou o
17
+ * par `getIssue`+`normalizeIssue`) de novo, ignorando que o detalhe acabou
18
+ * de buscar a mesma issue segundos antes.
19
+ *
20
+ * (1) evita uma segunda chamada de rede redundante (a issue não muda entre o
21
+ * usuário abrir o detalhe e pressionar `e`) e é consistente com a decisão já
22
+ * tomada pela #31 de manter o modelo `Issue` em memória em vez de só um bundle
23
+ * serializado. O roteador (`../app.tsx`) só mantém montado o TOPO da pilha —
24
+ * ao empilhar `export` a partir do detalhe, `IssueDetailScreen` é desmontada,
25
+ * então a issue precisa sobreviver em algo ACIMA da pilha, daí o contexto
26
+ * (mesmo raciocínio do `HomeSelectionProvider`, ver o JSDoc daquele módulo).
27
+ */
28
+ import { createContext, useCallback, useContext, useState } from 'react';
29
+ const LoadedIssueContext = createContext(undefined);
30
+ /**
31
+ * Provider do contexto de issue em memória — instanciado uma vez em
32
+ * `../app.tsx`, envolvendo toda a árvore de telas (ver o JSDoc do módulo).
33
+ */
34
+ export function LoadedIssueProvider({ children }) {
35
+ const [issue, setIssueState] = useState(undefined);
36
+ // Identidade ESTÁVEL (useCallback sem deps, padrão do repo) — evita
37
+ // resubscrições desnecessárias em quem a usa como dependência de efeito
38
+ // (ex.: `./issue-detail.tsx`).
39
+ const setIssue = useCallback((next) => {
40
+ setIssueState(next);
41
+ }, []);
42
+ const value = { issue, setIssue };
43
+ return _jsx(LoadedIssueContext.Provider, { value: value, children: children });
44
+ }
45
+ /**
46
+ * Hook de issue em memória consumido por `./issue-detail.tsx`/`./export.tsx`.
47
+ *
48
+ * @throws {Error} Se chamado fora de um `<LoadedIssueProvider>` — indica erro
49
+ * de composição (tela renderizada sem passar pelo roteador).
50
+ */
51
+ export function useLoadedIssue() {
52
+ const value = useContext(LoadedIssueContext);
53
+ if (value === undefined) {
54
+ throw new Error('useLoadedIssue() usado fora de <LoadedIssueProvider>.');
55
+ }
56
+ return value;
57
+ }
@@ -0,0 +1,2 @@
1
+ /** Tela de onboarding: colar api_key — Enter valida e confirma, Esc volta (atalho global, ver `../../app.tsx`). */
2
+ export declare function OnboardingApiKeyScreen(): import("react").JSX.Element;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Tela de onboarding: colar api_key diretamente (#28) — alcançável de dois
4
+ * jeitos: (1) escolha direta na tela de modo (`mode.tsx`, opção "Colar uma
5
+ * api_key"), ou (2) fallback automático quando o login por senha falha com
6
+ * 401/2FA (`validating.tsx` faz `replace('onboarding-api-key')`, ADR-003).
7
+ *
8
+ * Campo único mascarado (mesmo `TextInput` do login por senha, `mask="•"` —
9
+ * a key nunca é ecoada em texto). Ao confirmar, a key é VALIDADA contra
10
+ * `GET /users/current.json` (via `callbacks.onApiKeySubmit`, wiring real em
11
+ * `../../hooks/use-onboarding-callbacks.ts`) ANTES de ser persistida —
12
+ * mesma disciplina do login por senha (nunca salva sem confirmar que a
13
+ * credencial funciona). Estado assíncrono gerenciado localmente (mesmo
14
+ * padrão de `../config.tsx` no fluxo de logout): idle → validating → error,
15
+ * com spinner próprio (`../../components/spinner.tsx`) durante a validação.
16
+ *
17
+ * M2-13 (issue #36): esta tela também é o fallback de 2FA (`validating.tsx`,
18
+ * `replace('onboarding-api-key')`) quando o login por senha de um re-auth
19
+ * falha com 401 — então o sucesso aqui também precisa checar
20
+ * `OnboardingContext.reAuth` e, se definido, voltar à tela de origem
21
+ * (`popTo`) e retomar a operação original (`resolveReAuth()`) em vez de
22
+ * seguir para a splash.
23
+ */
24
+ import { Box, Text } from 'ink';
25
+ import { useState } from 'react';
26
+ import { Spinner } from '../../components/spinner.js';
27
+ import { TextInput } from '../../components/text-input.js';
28
+ import { glyphs } from '../../glyphs.js';
29
+ import { useNavigation } from '../../navigation.js';
30
+ import { symbols } from '../../symbols.js';
31
+ import { useTheme } from '../../theme.js';
32
+ import { useOnboarding } from './onboarding-context.js';
33
+ /** Tela de onboarding: colar api_key — Enter valida e confirma, Esc volta (atalho global, ver `../../app.tsx`). */
34
+ export function OnboardingApiKeyScreen() {
35
+ const theme = useTheme();
36
+ const { replace, popTo } = useNavigation();
37
+ const { callbacks, setUser, reAuth, resolveReAuth } = useOnboarding();
38
+ const [apiKey, setApiKey] = useState('');
39
+ const [status, setStatus] = useState('idle');
40
+ const [errorMessage, setErrorMessage] = useState(undefined);
41
+ function handleSubmit(value) {
42
+ setStatus('validating');
43
+ setErrorMessage(undefined);
44
+ callbacks.onApiKeySubmit(value).then((outcome) => {
45
+ if (outcome.kind === 'success') {
46
+ setUser(outcome.user);
47
+ if (reAuth !== undefined) {
48
+ // M2-13 (#36): volta à tela de origem e retoma a operação
49
+ // original — ver o mesmo desvio em `validating.tsx`.
50
+ popTo(reAuth.origin);
51
+ resolveReAuth();
52
+ return;
53
+ }
54
+ replace('onboarding-success');
55
+ return;
56
+ }
57
+ setStatus('error');
58
+ setErrorMessage(outcome.message);
59
+ }, (cause) => {
60
+ // Reason: defesa em profundidade — `onApiKeySubmit` (ver
61
+ // `../../hooks/use-onboarding-callbacks.ts`) já converte qualquer
62
+ // falha num `OnboardingLoginOutcome` resolvido; isso só protege
63
+ // contra um callback injetado incorretamente (ex.: em teste).
64
+ setStatus('error');
65
+ setErrorMessage(cause instanceof Error ? cause.message : String(cause));
66
+ });
67
+ }
68
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, borderStyle: "round", borderColor: theme.border, children: [_jsx(Text, { color: theme.primary, children: "Colar api_key" }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.muted, children: [symbols.pointer, " api_key: "] }), _jsx(TextInput, { value: apiKey, onChange: setApiKey, onSubmit: handleSubmit, mask: glyphs.maskBullet, isActive: status !== 'validating' })] }), status === 'validating' ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.primary, children: [_jsx(Spinner, {}), " Validando api_key..."] }) })) : null, status === 'error' && errorMessage !== undefined ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.danger, children: [symbols.cross, " ", errorMessage] }) })) : null, _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: ["Pressione", ' ', _jsx(Text, { color: theme.accent, children: "Enter" }), ' ', "para confirmar,", ' ', _jsx(Text, { color: theme.accent, children: "Esc" }), ' ', "para voltar."] }) })] }));
69
+ }
@@ -0,0 +1,2 @@
1
+ /** Tela de onboarding: login por usuário/senha — Esc volta (atalho global, ver `../../app.tsx`). */
2
+ export declare function OnboardingLoginScreen(): import("react").JSX.Element;
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Tela de onboarding 3/3: login por usuário e senha (M2-05.1, issue #27).
4
+ *
5
+ * Dois campos (`TextInput`, `../../components/text-input.tsx`): usuário com
6
+ * eco normal, senha mascarada (`mask="•"`, nunca ecoada em texto — ver
7
+ * `text-input.tsx`). `Tab`/setas alternam o campo ativo — o `useInput()` do
8
+ * Ink não tem noção de foco, então só um `TextInput` fica `isActive` por
9
+ * vez, senão a mesma tecla digitaria nos dois campos ao mesmo tempo. Enter
10
+ * no campo de usuário avança para a senha; Enter na senha confirma o
11
+ * formulário: dispara `callbacks.onLoginSubmit` (wiring real a
12
+ * `loginWithPassword` do core em `../../hooks/use-onboarding-callbacks.ts`,
13
+ * #28), guarda a `Promise` resultante em `pendingLogin` (`OnboardingContext`)
14
+ * e navega para `onboarding-validating`, que consome essa promise e decide a
15
+ * próxima tela (splash de sucesso, fallback de api_key em 401/2FA, ou
16
+ * mensagem de erro de rede).
17
+ */
18
+ import { Box, Text, useInput } from 'ink';
19
+ import { useState, useCallback } from 'react';
20
+ import { TextInput } from '../../components/text-input.js';
21
+ import { glyphs } from '../../glyphs.js';
22
+ import { useNavigation } from '../../navigation.js';
23
+ import { symbols } from '../../symbols.js';
24
+ import { useTheme } from '../../theme.js';
25
+ import { useOnboarding } from './onboarding-context.js';
26
+ /** Alterna entre os dois campos do formulário. */
27
+ function otherField(field) {
28
+ return field === 'username' ? 'password' : 'username';
29
+ }
30
+ /** Tela de onboarding: login por usuário/senha — Esc volta (atalho global, ver `../../app.tsx`). */
31
+ export function OnboardingLoginScreen() {
32
+ const theme = useTheme();
33
+ const { push } = useNavigation();
34
+ const { callbacks, setPendingLogin } = useOnboarding();
35
+ const [username, setUsername] = useState('');
36
+ const [password, setPassword] = useState('');
37
+ const [field, setField] = useState('username');
38
+ // Handler estável — setField (setState) já tem identidade fixa; o useCallback
39
+ // garante que o useInput subscreva uma única vez (padrão do repo).
40
+ const handleFieldSwitch = useCallback((_input, key) => {
41
+ if (key.tab || key.downArrow || key.upArrow) {
42
+ setField(otherField);
43
+ }
44
+ }, []);
45
+ useInput(handleFieldSwitch);
46
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, borderStyle: "round", borderColor: theme.border, children: [_jsx(Text, { color: theme.primary, children: "Login no Redmine" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: field === 'username' ? theme.primary : theme.muted, children: [field === 'username' ? symbols.pointer : ' ', " Usu\u00E1rio:", ' '] }), _jsx(TextInput, { value: username, onChange: setUsername, onSubmit: () => setField('password'), isActive: field === 'username' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: field === 'password' ? theme.primary : theme.muted, children: [field === 'password' ? symbols.pointer : ' ', " Senha:", ' '] }), _jsx(TextInput, { value: password, onChange: setPassword, onSubmit: () => {
47
+ setPendingLogin(callbacks.onLoginSubmit({ username, password }));
48
+ push('onboarding-validating');
49
+ }, mask: glyphs.maskBullet, isActive: field === 'password' })] })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: [_jsx(Text, { color: theme.accent, children: "Tab" }), ' ', "alterna o campo,", ' ', _jsx(Text, { color: theme.accent, children: "Enter" }), ' ', "confirma,", ' ', _jsx(Text, { color: theme.accent, children: "Esc" }), ' ', "volta."] }) })] }));
50
+ }
@@ -0,0 +1,2 @@
1
+ /** Tela de onboarding: modo de autenticação — setas/`j`/`k` navegam, Enter confirma, Esc volta (atalho global, ver `../../app.tsx`). */
2
+ export declare function OnboardingModeScreen(): import("react").JSX.Element;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Tela de onboarding 2/3: modo de autenticação (M2-05.1, issue #27).
4
+ *
5
+ * Seleção entre login por usuário/senha (avança para `onboarding-login`) e
6
+ * colar uma api_key diretamente (avança para `onboarding-api-key`, #28).
7
+ * `useListNavigation` (M2-04) dá o teclado padrão de lista (setas/`j`/`k`/
8
+ * Enter) de graça — nenhuma reimplementação aqui.
9
+ */
10
+ import { Box, Text } from 'ink';
11
+ import { glyphs } from '../../glyphs.js';
12
+ import { useListNavigation } from '../../hooks/use-list-navigation.js';
13
+ import { useNavigation } from '../../navigation.js';
14
+ import { symbols } from '../../symbols.js';
15
+ import { useTheme } from '../../theme.js';
16
+ import { useOnboarding } from './onboarding-context.js';
17
+ /** As duas formas de autenticação suportadas pelo onboarding. */
18
+ const MODE_OPTIONS = [
19
+ { mode: 'password', label: 'Entrar com usuário e senha' },
20
+ { mode: 'api-key', label: 'Colar uma api_key' },
21
+ ];
22
+ /** Tela de onboarding: modo de autenticação — setas/`j`/`k` navegam, Enter confirma, Esc volta (atalho global, ver `../../app.tsx`). */
23
+ export function OnboardingModeScreen() {
24
+ const { push } = useNavigation();
25
+ const theme = useTheme();
26
+ const { setMode, callbacks } = useOnboarding();
27
+ const { selectedIndex } = useListNavigation(MODE_OPTIONS.length, {
28
+ onSelect: (index) => {
29
+ const option = MODE_OPTIONS[index];
30
+ if (option === undefined) {
31
+ return;
32
+ }
33
+ setMode(option.mode);
34
+ callbacks.onModeSelect(option.mode);
35
+ push(option.mode === 'password' ? 'onboarding-login' : 'onboarding-api-key');
36
+ },
37
+ });
38
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, borderStyle: "round", borderColor: theme.border, children: [_jsx(Text, { color: theme.primary, children: "Como voc\u00EA quer entrar?" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: MODE_OPTIONS.map((option, index) => {
39
+ const isSelected = index === selectedIndex;
40
+ return (_jsxs(Text, { color: isSelected ? theme.primary : theme.muted, children: [isSelected ? symbols.pointer : ' ', " ", option.label] }, option.mode));
41
+ }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: [_jsx(Text, { color: theme.accent, children: `${glyphs.arrowUp}${glyphs.arrowDown}/j/k` }), ' ', "navegam,", ' ', _jsx(Text, { color: theme.accent, children: "Enter" }), ' ', "confirma,", ' ', _jsx(Text, { color: theme.accent, children: "Esc" }), ' ', "volta."] }) })] }));
42
+ }