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.
- package/LICENSE +21 -0
- package/README.md +449 -0
- package/dist/bundle/index.d.ts +5 -0
- package/dist/bundle/index.js +5 -0
- package/dist/bundle/json.d.ts +90 -0
- package/dist/bundle/json.js +266 -0
- package/dist/bundle/markdown.d.ts +75 -0
- package/dist/bundle/markdown.js +294 -0
- package/dist/bundle/search-list.d.ts +43 -0
- package/dist/bundle/search-list.js +53 -0
- package/dist/bundle/stable-stringify.d.ts +26 -0
- package/dist/bundle/stable-stringify.js +50 -0
- package/dist/cache/contract.d.ts +157 -0
- package/dist/cache/contract.js +0 -0
- package/dist/cache/disk-index.d.ts +82 -0
- package/dist/cache/disk-index.js +220 -0
- package/dist/cache/disk.d.ts +133 -0
- package/dist/cache/disk.js +313 -0
- package/dist/cache/gc.d.ts +78 -0
- package/dist/cache/gc.js +123 -0
- package/dist/cache/get-or-compute.d.ts +36 -0
- package/dist/cache/get-or-compute.js +52 -0
- package/dist/cache/index.d.ts +9 -0
- package/dist/cache/index.js +8 -0
- package/dist/cache/keys.d.ts +76 -0
- package/dist/cache/keys.js +78 -0
- package/dist/cache/memory.d.ts +48 -0
- package/dist/cache/memory.js +110 -0
- package/dist/cache-first.d.ts +127 -0
- package/dist/cache-first.js +227 -0
- package/dist/client/errors.d.ts +33 -0
- package/dist/client/errors.js +49 -0
- package/dist/client/http.d.ts +110 -0
- package/dist/client/http.js +207 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +5 -0
- package/dist/client/issues.d.ts +71 -0
- package/dist/client/issues.js +100 -0
- package/dist/client/search.d.ts +58 -0
- package/dist/client/search.js +81 -0
- package/dist/config/credentials.d.ts +247 -0
- package/dist/config/credentials.js +427 -0
- package/dist/config/doctor.d.ts +123 -0
- package/dist/config/doctor.js +260 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.js +6 -0
- package/dist/config/keyring.d.ts +96 -0
- package/dist/config/keyring.js +158 -0
- package/dist/config/login.d.ts +97 -0
- package/dist/config/login.js +189 -0
- package/dist/config/settings.d.ts +94 -0
- package/dist/config/settings.js +140 -0
- package/dist/contract.d.ts +173 -0
- package/dist/contract.js +27 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +8 -0
- package/dist/extract/audio-extractor.d.ts +105 -0
- package/dist/extract/audio-extractor.js +156 -0
- package/dist/extract/audio.d.ts +126 -0
- package/dist/extract/audio.js +184 -0
- package/dist/extract/dispatcher.d.ts +132 -0
- package/dist/extract/dispatcher.js +115 -0
- package/dist/extract/download.d.ts +111 -0
- package/dist/extract/download.js +261 -0
- package/dist/extract/duration.d.ts +106 -0
- package/dist/extract/duration.js +148 -0
- package/dist/extract/ffmpeg.d.ts +56 -0
- package/dist/extract/ffmpeg.js +95 -0
- package/dist/extract/gguf.d.ts +137 -0
- package/dist/extract/gguf.js +215 -0
- package/dist/extract/index.d.ts +19 -0
- package/dist/extract/index.js +19 -0
- package/dist/extract/magic.d.ts +80 -0
- package/dist/extract/magic.js +282 -0
- package/dist/extract/ooxml.d.ts +131 -0
- package/dist/extract/ooxml.js +336 -0
- package/dist/extract/pdf.d.ts +147 -0
- package/dist/extract/pdf.js +322 -0
- package/dist/extract/queue.d.ts +167 -0
- package/dist/extract/queue.js +217 -0
- package/dist/extract/subprocess.d.ts +145 -0
- package/dist/extract/subprocess.js +181 -0
- package/dist/extract/tesseract.d.ts +153 -0
- package/dist/extract/tesseract.js +321 -0
- package/dist/extract/video-extractor.d.ts +84 -0
- package/dist/extract/video-extractor.js +89 -0
- package/dist/extract/video.d.ts +198 -0
- package/dist/extract/video.js +418 -0
- package/dist/extract/which.d.ts +63 -0
- package/dist/extract/which.js +72 -0
- package/dist/extract/whisper-extract.d.ts +211 -0
- package/dist/extract/whisper-extract.js +323 -0
- package/dist/extract/whisper.d.ts +50 -0
- package/dist/extract/whisper.js +67 -0
- package/dist/extract/zip.d.ts +50 -0
- package/dist/extract/zip.js +165 -0
- package/dist/extract-issue-attachments.d.ts +82 -0
- package/dist/extract-issue-attachments.js +156 -0
- package/dist/fetch-attachment-text.d.ts +113 -0
- package/dist/fetch-attachment-text.js +155 -0
- package/dist/fetch-issue-bundle.d.ts +81 -0
- package/dist/fetch-issue-bundle.js +93 -0
- package/dist/fetch-issue-search.d.ts +74 -0
- package/dist/fetch-issue-search.js +120 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +67 -0
- package/dist/normalize/collections.d.ts +52 -0
- package/dist/normalize/collections.js +170 -0
- package/dist/normalize/helpers.d.ts +35 -0
- package/dist/normalize/helpers.js +59 -0
- package/dist/normalize/index.d.ts +2 -0
- package/dist/normalize/index.js +2 -0
- package/dist/normalize/issue.d.ts +34 -0
- package/dist/normalize/issue.js +154 -0
- package/dist/surfaces/cli/commands.d.ts +61 -0
- package/dist/surfaces/cli/commands.js +262 -0
- package/dist/surfaces/cli/main.d.ts +32 -0
- package/dist/surfaces/cli/main.js +210 -0
- package/dist/surfaces/cli/prompts.d.ts +66 -0
- package/dist/surfaces/cli/prompts.js +147 -0
- package/dist/surfaces/cli/tty.d.ts +27 -0
- package/dist/surfaces/cli/tty.js +35 -0
- package/dist/surfaces/cli/types.d.ts +39 -0
- package/dist/surfaces/cli/types.js +7 -0
- package/dist/surfaces/mcp/server.d.ts +171 -0
- package/dist/surfaces/mcp/server.js +427 -0
- package/dist/surfaces/tui/app.d.ts +55 -0
- package/dist/surfaces/tui/app.js +180 -0
- package/dist/surfaces/tui/attachment-status.d.ts +79 -0
- package/dist/surfaces/tui/attachment-status.js +113 -0
- package/dist/surfaces/tui/components/breadcrumb.d.ts +7 -0
- package/dist/surfaces/tui/components/breadcrumb.js +31 -0
- package/dist/surfaces/tui/components/gradient-text.d.ts +23 -0
- package/dist/surfaces/tui/components/gradient-text.js +75 -0
- package/dist/surfaces/tui/components/scroll-view.d.ts +25 -0
- package/dist/surfaces/tui/components/scroll-view.js +77 -0
- package/dist/surfaces/tui/components/spinner.d.ts +12 -0
- package/dist/surfaces/tui/components/spinner.js +39 -0
- package/dist/surfaces/tui/components/text-input.d.ts +45 -0
- package/dist/surfaces/tui/components/text-input.js +114 -0
- package/dist/surfaces/tui/format-file-size.d.ts +24 -0
- package/dist/surfaces/tui/format-file-size.js +43 -0
- package/dist/surfaces/tui/glyphs.d.ts +47 -0
- package/dist/surfaces/tui/glyphs.js +84 -0
- package/dist/surfaces/tui/hooks/use-auth-guard.d.ts +39 -0
- package/dist/surfaces/tui/hooks/use-auth-guard.js +135 -0
- package/dist/surfaces/tui/hooks/use-doctor-status.d.ts +64 -0
- package/dist/surfaces/tui/hooks/use-doctor-status.js +123 -0
- package/dist/surfaces/tui/hooks/use-escape-interceptor.d.ts +25 -0
- package/dist/surfaces/tui/hooks/use-escape-interceptor.js +65 -0
- package/dist/surfaces/tui/hooks/use-exit-guard.d.ts +18 -0
- package/dist/surfaces/tui/hooks/use-exit-guard.js +66 -0
- package/dist/surfaces/tui/hooks/use-export-bundle.d.ts +62 -0
- package/dist/surfaces/tui/hooks/use-export-bundle.js +100 -0
- package/dist/surfaces/tui/hooks/use-issue-detail.d.ts +61 -0
- package/dist/surfaces/tui/hooks/use-issue-detail.js +132 -0
- package/dist/surfaces/tui/hooks/use-issue-search.d.ts +71 -0
- package/dist/surfaces/tui/hooks/use-issue-search.js +168 -0
- package/dist/surfaces/tui/hooks/use-list-navigation.d.ts +44 -0
- package/dist/surfaces/tui/hooks/use-list-navigation.js +82 -0
- package/dist/surfaces/tui/hooks/use-media-binaries.d.ts +24 -0
- package/dist/surfaces/tui/hooks/use-media-binaries.js +44 -0
- package/dist/surfaces/tui/hooks/use-my-issues.d.ts +66 -0
- package/dist/surfaces/tui/hooks/use-my-issues.js +151 -0
- package/dist/surfaces/tui/hooks/use-onboarding-callbacks.d.ts +11 -0
- package/dist/surfaces/tui/hooks/use-onboarding-callbacks.js +104 -0
- package/dist/surfaces/tui/hooks/use-terminal-width.d.ts +52 -0
- package/dist/surfaces/tui/hooks/use-terminal-width.js +90 -0
- package/dist/surfaces/tui/index.d.ts +50 -0
- package/dist/surfaces/tui/index.js +158 -0
- package/dist/surfaces/tui/instance.d.ts +37 -0
- package/dist/surfaces/tui/instance.js +36 -0
- package/dist/surfaces/tui/job-registry.d.ts +113 -0
- package/dist/surfaces/tui/job-registry.js +123 -0
- package/dist/surfaces/tui/job-status.d.ts +45 -0
- package/dist/surfaces/tui/job-status.js +81 -0
- package/dist/surfaces/tui/navigation.d.ts +74 -0
- package/dist/surfaces/tui/navigation.js +87 -0
- package/dist/surfaces/tui/palettes.d.ts +38 -0
- package/dist/surfaces/tui/palettes.js +244 -0
- package/dist/surfaces/tui/screen.d.ts +30 -0
- package/dist/surfaces/tui/screen.js +51 -0
- package/dist/surfaces/tui/screens/about.d.ts +2 -0
- package/dist/surfaces/tui/screens/about.js +35 -0
- package/dist/surfaces/tui/screens/appearance.d.ts +2 -0
- package/dist/surfaces/tui/screens/appearance.js +74 -0
- package/dist/surfaces/tui/screens/config.d.ts +2 -0
- package/dist/surfaces/tui/screens/config.js +82 -0
- package/dist/surfaces/tui/screens/doctor.d.ts +2 -0
- package/dist/surfaces/tui/screens/doctor.js +109 -0
- package/dist/surfaces/tui/screens/export.d.ts +2 -0
- package/dist/surfaces/tui/screens/export.js +168 -0
- package/dist/surfaces/tui/screens/home-selection.d.ts +70 -0
- package/dist/surfaces/tui/screens/home-selection.js +80 -0
- package/dist/surfaces/tui/screens/home.d.ts +2 -0
- package/dist/surfaces/tui/screens/home.js +200 -0
- package/dist/surfaces/tui/screens/issue-detail.d.ts +6 -0
- package/dist/surfaces/tui/screens/issue-detail.js +182 -0
- package/dist/surfaces/tui/screens/jobs.d.ts +7 -0
- package/dist/surfaces/tui/screens/jobs.js +89 -0
- package/dist/surfaces/tui/screens/loaded-issue-context.d.ts +49 -0
- package/dist/surfaces/tui/screens/loaded-issue-context.js +57 -0
- package/dist/surfaces/tui/screens/onboarding/api-key.d.ts +2 -0
- package/dist/surfaces/tui/screens/onboarding/api-key.js +69 -0
- package/dist/surfaces/tui/screens/onboarding/login.d.ts +2 -0
- package/dist/surfaces/tui/screens/onboarding/login.js +50 -0
- package/dist/surfaces/tui/screens/onboarding/mode.d.ts +2 -0
- package/dist/surfaces/tui/screens/onboarding/mode.js +42 -0
- package/dist/surfaces/tui/screens/onboarding/onboarding-context.d.ts +221 -0
- package/dist/surfaces/tui/screens/onboarding/onboarding-context.js +131 -0
- package/dist/surfaces/tui/screens/onboarding/success.d.ts +2 -0
- package/dist/surfaces/tui/screens/onboarding/success.js +41 -0
- package/dist/surfaces/tui/screens/onboarding/url.d.ts +24 -0
- package/dist/surfaces/tui/screens/onboarding/url.js +84 -0
- package/dist/surfaces/tui/screens/onboarding/validating.d.ts +1 -0
- package/dist/surfaces/tui/screens/onboarding/validating.js +86 -0
- package/dist/surfaces/tui/screens/welcome.d.ts +6 -0
- package/dist/surfaces/tui/screens/welcome.js +95 -0
- package/dist/surfaces/tui/status-color.d.ts +21 -0
- package/dist/surfaces/tui/status-color.js +23 -0
- package/dist/surfaces/tui/symbols.d.ts +231 -0
- package/dist/surfaces/tui/symbols.js +14 -0
- package/dist/surfaces/tui/terminal-colors.d.ts +29 -0
- package/dist/surfaces/tui/terminal-colors.js +39 -0
- package/dist/surfaces/tui/theme.d.ts +156 -0
- package/dist/surfaces/tui/theme.js +86 -0
- package/dist/surfaces/tui/truncate.d.ts +31 -0
- package/dist/surfaces/tui/truncate.js +81 -0
- package/package.json +93 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Estado local do fluxo de onboarding (M2-05.1, issue #27, evoluído na #28)
|
|
3
|
+
* URL da instância + modo de autenticação + a resolução do login, tudo
|
|
4
|
+
* compartilhado entre as telas do fluxo (`url.tsx` → `mode.tsx` →
|
|
5
|
+
* `login.tsx`/`api-key.tsx` → `validating.tsx` → `success.tsx`) sem
|
|
6
|
+
* prop-drilling — como toda tela do roteador é renderizada sem props
|
|
7
|
+
* (`../../screen.ts`), o único jeito de uma tela passar estado para a
|
|
8
|
+
* próxima é via contexto.
|
|
9
|
+
*
|
|
10
|
+
* Os callbacks de submissão (`onUrlSubmit`, `onModeSelect`, `onLoginSubmit`,
|
|
11
|
+
* `onApiKeySubmit`) são injetáveis, todos no-op por padrão — o wiring real
|
|
12
|
+
* (chamada a `loginWithPassword`/`validateApiKey` do core + persistência via
|
|
13
|
+
* `MigratingCredentialCascade`) vive em `../../hooks/use-onboarding-callbacks.ts`,
|
|
14
|
+
* o único ponto que importa o core para este fluxo (ADR-005); quem instancia
|
|
15
|
+
* o provider com os callbacks reais é `../../app.tsx`.
|
|
16
|
+
*
|
|
17
|
+
* M2-13 (issue #36) acrescenta o MODO RE-AUTH (`reAuth`/`beginReAuth`/
|
|
18
|
+
* `resolveReAuth`/`abortReAuth`): quando uma tela de dados (#29+) recebe um
|
|
19
|
+
* 401 (`RedmineAuthError`) numa chamada ao core, ela usa
|
|
20
|
+
* `../../hooks/use-auth-guard.ts` para empilhar `onboarding-login` de novo,
|
|
21
|
+
* reaproveitando este mesmo fluxo de login para o usuário reautenticar sem
|
|
22
|
+
* perder o que estava fazendo — ver o JSDoc de `useAuthGuard` para o
|
|
23
|
+
* contrato completo consumido pelas telas de dados, e `validating.tsx`/
|
|
24
|
+
* `api-key.tsx` para onde o sucesso do re-login é detectado e a operação
|
|
25
|
+
* original retomada.
|
|
26
|
+
*
|
|
27
|
+
* Fix do review do PR #119: dois 401 concorrentes (duas telas de dados, ou
|
|
28
|
+
* a mesma tela com duas chamadas em voo) podem cair em re-auth ao mesmo
|
|
29
|
+
* tempo — em vez de o segundo `beginReAuth` sobrescrever silenciosamente o
|
|
30
|
+
* primeiro (perdendo seu retry/reject para sempre), `reAuth` agora acumula
|
|
31
|
+
* uma LISTA de pendências (`pending`); uma única passagem de login bem
|
|
32
|
+
* sucedida atende todas elas (`resolveReAuth`). Esc durante o re-login
|
|
33
|
+
* (`../../app.tsx`) agora também tem uma saída explícita: `abortReAuth`
|
|
34
|
+
* rejeita todas as pendências em vez de deixar a(s) `Promise`(s) do
|
|
35
|
+
* `guard()` penduradas para sempre.
|
|
36
|
+
*/
|
|
37
|
+
import { type ReactNode } from 'react';
|
|
38
|
+
import type { ScreenName } from '../../screen.js';
|
|
39
|
+
/** Modo de autenticação escolhido na tela de modo (`mode.tsx`). */
|
|
40
|
+
export type OnboardingMode = 'password' | 'api-key';
|
|
41
|
+
/** Credenciais coletadas na tela de login (`login.tsx`). */
|
|
42
|
+
export interface OnboardingLoginCredentials {
|
|
43
|
+
/** Usuário/login digitado (eco normal). */
|
|
44
|
+
username: string;
|
|
45
|
+
/** Senha digitada (mascarada na tela, nunca ecoada em texto). */
|
|
46
|
+
password: string;
|
|
47
|
+
}
|
|
48
|
+
/** Identidade mínima do usuário autenticado — nunca inclui a api_key. */
|
|
49
|
+
export interface OnboardingUser {
|
|
50
|
+
/** Id numérico do usuário no Redmine. */
|
|
51
|
+
id: number;
|
|
52
|
+
/** Login do usuário. */
|
|
53
|
+
login: string;
|
|
54
|
+
/** Nome de exibição (`firstname lastname`, ou `login` se ausentes). */
|
|
55
|
+
name: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resultado de uma tentativa de login (por senha ou api_key colada),
|
|
59
|
+
* classificado pelo que a tela de validação (`validating.tsx`) precisa
|
|
60
|
+
* decidir: mostrar a splash de sucesso, oferecer o fallback de api_key
|
|
61
|
+
* (401/2FA), ou mostrar uma mensagem de erro acionável (rede/TLS).
|
|
62
|
+
*/
|
|
63
|
+
export type OnboardingLoginOutcome = {
|
|
64
|
+
kind: 'success';
|
|
65
|
+
user: OnboardingUser;
|
|
66
|
+
} | {
|
|
67
|
+
kind: 'auth-error';
|
|
68
|
+
message: string;
|
|
69
|
+
} | {
|
|
70
|
+
kind: 'network-error';
|
|
71
|
+
message: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Uma pendência individual de re-auth (fix do review #119) — um par
|
|
75
|
+
* retry/reject correspondente a UMA chamada de `guard()`
|
|
76
|
+
* (`../../hooks/use-auth-guard.ts`) que recebeu 401. `OnboardingReAuth`
|
|
77
|
+
* acumula uma lista destas: com dois 401 concorrentes, ambas as pendências
|
|
78
|
+
* são atendidas por uma única passagem de login (`resolveReAuth`) ou
|
|
79
|
+
* canceladas juntas (`abortReAuth`).
|
|
80
|
+
*/
|
|
81
|
+
export interface OnboardingReAuthPending {
|
|
82
|
+
/**
|
|
83
|
+
* Reexecuta a operação original que falhou com 401. Chamado automaticamente
|
|
84
|
+
* por `resolveReAuth()` quando o login é refeito com sucesso — nenhuma tela
|
|
85
|
+
* precisa chamá-lo diretamente.
|
|
86
|
+
*/
|
|
87
|
+
retry: () => void;
|
|
88
|
+
/**
|
|
89
|
+
* Rejeita a `Promise` de `guard()` correspondente a esta pendência com o
|
|
90
|
+
* motivo do abandono (tipicamente `ReAuthAbortedError`,
|
|
91
|
+
* `../../hooks/use-auth-guard.ts`). Chamado automaticamente por
|
|
92
|
+
* `abortReAuth()` — nenhuma tela precisa chamá-lo diretamente.
|
|
93
|
+
*/
|
|
94
|
+
reject: (reason: Error) => void;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Estado de um fluxo de re-autenticação em andamento (M2-13, issue #36) —
|
|
98
|
+
* criado por `useAuthGuard` (`../../hooks/use-auth-guard.ts`) ao capturar um
|
|
99
|
+
* `RedmineAuthError` (401) numa operação de uma tela de dados (#29+).
|
|
100
|
+
*/
|
|
101
|
+
export interface OnboardingReAuth {
|
|
102
|
+
/**
|
|
103
|
+
* Tela que estava ativa quando o PRIMEIRO 401 deste ciclo aconteceu — a
|
|
104
|
+
* navegação volta para ela (`popTo`, `../../navigation.tsx`) assim que o
|
|
105
|
+
* re-login for concluído (com sucesso) ou abandonado (Esc). Pendências
|
|
106
|
+
* subsequentes (401 concorrentes) reaproveitam esta mesma origem — a TUI
|
|
107
|
+
* só sustenta uma tela de dados ativa por vez, então não há origem
|
|
108
|
+
* divergente de fato.
|
|
109
|
+
*/
|
|
110
|
+
origin: ScreenName;
|
|
111
|
+
/**
|
|
112
|
+
* Todas as pendências acumuladas desde que o modo re-auth foi ativado
|
|
113
|
+
* (fix do review #119) — ver {@link OnboardingReAuthPending}.
|
|
114
|
+
*/
|
|
115
|
+
pending: readonly OnboardingReAuthPending[];
|
|
116
|
+
}
|
|
117
|
+
/** Callbacks de submissão injetáveis — todos no-op por padrão (wiring real na #28, ver topo do arquivo). */
|
|
118
|
+
export interface OnboardingCallbacks {
|
|
119
|
+
/** Chamado quando a URL é validada (esquema) e confirmada (Enter na tela de URL). */
|
|
120
|
+
onUrlSubmit: (url: string) => void;
|
|
121
|
+
/** Chamado quando o usuário escolhe um modo de autenticação. */
|
|
122
|
+
onModeSelect: (mode: OnboardingMode) => void;
|
|
123
|
+
/**
|
|
124
|
+
* Chamado quando o formulário de login (usuário/senha) é confirmado.
|
|
125
|
+
* Resolve para o {@link OnboardingLoginOutcome} — consumido por
|
|
126
|
+
* `validating.tsx` via `pendingLogin`.
|
|
127
|
+
*/
|
|
128
|
+
onLoginSubmit: (credentials: OnboardingLoginCredentials) => Promise<OnboardingLoginOutcome>;
|
|
129
|
+
/**
|
|
130
|
+
* Chamado quando uma api_key colada é confirmada (`api-key.tsx`) — valida
|
|
131
|
+
* a key contra o Redmine ANTES de salvar.
|
|
132
|
+
*/
|
|
133
|
+
onApiKeySubmit: (apiKey: string) => Promise<OnboardingLoginOutcome>;
|
|
134
|
+
}
|
|
135
|
+
/** Valor exposto pelo contexto de onboarding. */
|
|
136
|
+
export interface OnboardingValue {
|
|
137
|
+
/** URL digitada na tela de URL — sobrevive à navegação para trás (Esc). */
|
|
138
|
+
url: string;
|
|
139
|
+
/** Atualiza a URL — ligado diretamente ao `onChange` do `TextInput` da tela de URL. */
|
|
140
|
+
setUrl: (url: string) => void;
|
|
141
|
+
/** Modo de autenticação escolhido, ou `undefined` antes da tela de modo. */
|
|
142
|
+
mode: OnboardingMode | undefined;
|
|
143
|
+
/** Atualiza o modo escolhido. */
|
|
144
|
+
setMode: (mode: OnboardingMode) => void;
|
|
145
|
+
/**
|
|
146
|
+
* Promise do login em andamento — definida por `login.tsx` ao confirmar o
|
|
147
|
+
* formulário (via `callbacks.onLoginSubmit`) e consumida por
|
|
148
|
+
* `validating.tsx`, que decide a próxima tela a partir do
|
|
149
|
+
* {@link OnboardingLoginOutcome} resolvido. `undefined` fora desse fluxo.
|
|
150
|
+
*/
|
|
151
|
+
pendingLogin: Promise<OnboardingLoginOutcome> | undefined;
|
|
152
|
+
/** Atualiza `pendingLogin`. */
|
|
153
|
+
setPendingLogin: (pending: Promise<OnboardingLoginOutcome>) => void;
|
|
154
|
+
/** Usuário autenticado com sucesso — preenchido por `validating.tsx`/`api-key.tsx`, exibido em `success.tsx`. */
|
|
155
|
+
user: OnboardingUser | undefined;
|
|
156
|
+
/** Atualiza `user`. */
|
|
157
|
+
setUser: (user: OnboardingUser) => void;
|
|
158
|
+
/** Callbacks de submissão injetáveis (ver {@link OnboardingCallbacks}). */
|
|
159
|
+
callbacks: OnboardingCallbacks;
|
|
160
|
+
/**
|
|
161
|
+
* Fluxo de re-autenticação em andamento (M2-13, issue #36), ou `undefined`
|
|
162
|
+
* fora dele. Consumido por `validating.tsx`/`api-key.tsx` para desviar do
|
|
163
|
+
* caminho normal de sucesso (splash) e, em vez disso, retomar a operação de
|
|
164
|
+
* origem — ver {@link OnboardingReAuth}.
|
|
165
|
+
*/
|
|
166
|
+
reAuth: OnboardingReAuth | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Inicia (ou ADICIONA a) o modo re-auth — chamado por `useAuthGuard()`
|
|
169
|
+
* (`../../hooks/use-auth-guard.ts`), nunca diretamente pelas telas.
|
|
170
|
+
*
|
|
171
|
+
* Fix do review #119: se já houver um `reAuth` ativo (401 concorrente),
|
|
172
|
+
* `pending` é apenas ACRESCENTADO à lista existente — a `origin` recebida
|
|
173
|
+
* aqui é ignorada nesse caso (a origem do ciclo já foi fixada na primeira
|
|
174
|
+
* chamada) — uma única passagem de login atende todas as pendências.
|
|
175
|
+
* Senão, ativa o modo com `origin` e uma lista iniciada só com `pending`.
|
|
176
|
+
*
|
|
177
|
+
* @returns `true` se este chamado ATIVOU o modo re-auth (nenhum estava em
|
|
178
|
+
* andamento antes) — o chamador (`useAuthGuard`) usa isso para decidir
|
|
179
|
+
* se deve empilhar `onboarding-login` (só na primeira vez; pendências
|
|
180
|
+
* subsequentes reaproveitam a tela já empilhada).
|
|
181
|
+
*/
|
|
182
|
+
beginReAuth: (origin: ScreenName, pending: OnboardingReAuthPending) => boolean;
|
|
183
|
+
/**
|
|
184
|
+
* Encerra o modo re-auth com SUCESSO: dispara `retry()` de TODAS as
|
|
185
|
+
* pendências acumuladas (fix do review #119 — antes só a mais recente era
|
|
186
|
+
* atendida) e limpa o estado. Chamado por `validating.tsx`/`api-key.tsx`
|
|
187
|
+
* quando o re-login resolve com `{ kind: 'success' }` enquanto `reAuth`
|
|
188
|
+
* está definido. No-op se não houver `reAuth` em andamento.
|
|
189
|
+
*/
|
|
190
|
+
resolveReAuth: () => void;
|
|
191
|
+
/**
|
|
192
|
+
* Encerra o modo re-auth com ABANDONO (fix do review #119 — Esc durante o
|
|
193
|
+
* re-login, ver `../../app.tsx`): rejeita TODAS as pendências acumuladas
|
|
194
|
+
* com `reason` (tipicamente `ReAuthAbortedError`,
|
|
195
|
+
* `../../hooks/use-auth-guard.ts`) e limpa o estado — sem isso, a(s)
|
|
196
|
+
* `Promise`(s) de `guard()` ficariam penduradas para sempre. No-op se não
|
|
197
|
+
* houver `reAuth` em andamento.
|
|
198
|
+
*/
|
|
199
|
+
abortReAuth: (reason: Error) => void;
|
|
200
|
+
}
|
|
201
|
+
/** Props do `OnboardingProvider`. */
|
|
202
|
+
export interface OnboardingProviderProps {
|
|
203
|
+
/** Callbacks reais de submissão (injetados pela #28); no-op por padrão. */
|
|
204
|
+
callbacks?: OnboardingCallbacks;
|
|
205
|
+
children: ReactNode;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Provider do contexto de onboarding — instanciado uma vez em `../../app.tsx`,
|
|
209
|
+
* envolvendo toda a árvore de telas (não só as de onboarding), para que
|
|
210
|
+
* `useOnboarding()` esteja sempre disponível quando o roteador troca para
|
|
211
|
+
* qualquer tela `onboarding-*`.
|
|
212
|
+
*/
|
|
213
|
+
export declare function OnboardingProvider({ callbacks, children }: OnboardingProviderProps): import("react").JSX.Element;
|
|
214
|
+
/**
|
|
215
|
+
* Hook de onboarding consumido pelas telas `url.tsx`/`mode.tsx`/`login.tsx`/
|
|
216
|
+
* `api-key.tsx`/`validating.tsx`/`success.tsx`.
|
|
217
|
+
*
|
|
218
|
+
* @throws {Error} Se chamado fora de um `<OnboardingProvider>` — indica erro
|
|
219
|
+
* de composição (tela renderizada sem passar pelo roteador).
|
|
220
|
+
*/
|
|
221
|
+
export declare function useOnboarding(): OnboardingValue;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Estado local do fluxo de onboarding (M2-05.1, issue #27, evoluído na #28)
|
|
4
|
+
* URL da instância + modo de autenticação + a resolução do login, tudo
|
|
5
|
+
* compartilhado entre as telas do fluxo (`url.tsx` → `mode.tsx` →
|
|
6
|
+
* `login.tsx`/`api-key.tsx` → `validating.tsx` → `success.tsx`) sem
|
|
7
|
+
* prop-drilling — como toda tela do roteador é renderizada sem props
|
|
8
|
+
* (`../../screen.ts`), o único jeito de uma tela passar estado para a
|
|
9
|
+
* próxima é via contexto.
|
|
10
|
+
*
|
|
11
|
+
* Os callbacks de submissão (`onUrlSubmit`, `onModeSelect`, `onLoginSubmit`,
|
|
12
|
+
* `onApiKeySubmit`) são injetáveis, todos no-op por padrão — o wiring real
|
|
13
|
+
* (chamada a `loginWithPassword`/`validateApiKey` do core + persistência via
|
|
14
|
+
* `MigratingCredentialCascade`) vive em `../../hooks/use-onboarding-callbacks.ts`,
|
|
15
|
+
* o único ponto que importa o core para este fluxo (ADR-005); quem instancia
|
|
16
|
+
* o provider com os callbacks reais é `../../app.tsx`.
|
|
17
|
+
*
|
|
18
|
+
* M2-13 (issue #36) acrescenta o MODO RE-AUTH (`reAuth`/`beginReAuth`/
|
|
19
|
+
* `resolveReAuth`/`abortReAuth`): quando uma tela de dados (#29+) recebe um
|
|
20
|
+
* 401 (`RedmineAuthError`) numa chamada ao core, ela usa
|
|
21
|
+
* `../../hooks/use-auth-guard.ts` para empilhar `onboarding-login` de novo,
|
|
22
|
+
* reaproveitando este mesmo fluxo de login para o usuário reautenticar sem
|
|
23
|
+
* perder o que estava fazendo — ver o JSDoc de `useAuthGuard` para o
|
|
24
|
+
* contrato completo consumido pelas telas de dados, e `validating.tsx`/
|
|
25
|
+
* `api-key.tsx` para onde o sucesso do re-login é detectado e a operação
|
|
26
|
+
* original retomada.
|
|
27
|
+
*
|
|
28
|
+
* Fix do review do PR #119: dois 401 concorrentes (duas telas de dados, ou
|
|
29
|
+
* a mesma tela com duas chamadas em voo) podem cair em re-auth ao mesmo
|
|
30
|
+
* tempo — em vez de o segundo `beginReAuth` sobrescrever silenciosamente o
|
|
31
|
+
* primeiro (perdendo seu retry/reject para sempre), `reAuth` agora acumula
|
|
32
|
+
* uma LISTA de pendências (`pending`); uma única passagem de login bem
|
|
33
|
+
* sucedida atende todas elas (`resolveReAuth`). Esc durante o re-login
|
|
34
|
+
* (`../../app.tsx`) agora também tem uma saída explícita: `abortReAuth`
|
|
35
|
+
* rejeita todas as pendências em vez de deixar a(s) `Promise`(s) do
|
|
36
|
+
* `guard()` penduradas para sempre.
|
|
37
|
+
*/
|
|
38
|
+
import { createContext, useCallback, useContext, useRef, useState } from 'react';
|
|
39
|
+
/** Callbacks padrão: no-op — usados quando `<OnboardingProvider>` não recebe `callbacks`. */
|
|
40
|
+
const NOOP_CALLBACKS = {
|
|
41
|
+
onUrlSubmit: () => undefined,
|
|
42
|
+
onModeSelect: () => undefined,
|
|
43
|
+
onLoginSubmit: () => Promise.resolve({ kind: 'network-error', message: 'onLoginSubmit não configurado.' }),
|
|
44
|
+
onApiKeySubmit: () => Promise.resolve({ kind: 'network-error', message: 'onApiKeySubmit não configurado.' }),
|
|
45
|
+
};
|
|
46
|
+
const OnboardingContext = createContext(undefined);
|
|
47
|
+
/**
|
|
48
|
+
* Provider do contexto de onboarding — instanciado uma vez em `../../app.tsx`,
|
|
49
|
+
* envolvendo toda a árvore de telas (não só as de onboarding), para que
|
|
50
|
+
* `useOnboarding()` esteja sempre disponível quando o roteador troca para
|
|
51
|
+
* qualquer tela `onboarding-*`.
|
|
52
|
+
*/
|
|
53
|
+
export function OnboardingProvider({ callbacks = NOOP_CALLBACKS, children }) {
|
|
54
|
+
const [url, setUrl] = useState('');
|
|
55
|
+
const [mode, setMode] = useState(undefined);
|
|
56
|
+
const [pendingLogin, setPendingLogin] = useState(undefined);
|
|
57
|
+
const [user, setUser] = useState(undefined);
|
|
58
|
+
const [reAuth, setReAuth] = useState(undefined);
|
|
59
|
+
// Ref espelhando `reAuth` (fix do review #119): `beginReAuth` precisa
|
|
60
|
+
// decidir SINCRONAMENTE (para devolver o `boolean` de ativação ao
|
|
61
|
+
// chamador) se já existe um ciclo em andamento, mas o setter funcional do
|
|
62
|
+
// `useState` só executa o callback no próximo render — não dá pra
|
|
63
|
+
// confiar nele para essa decisão. A ref é a única fonte de verdade
|
|
64
|
+
// síncrona; `setReAuth` continua sendo chamado em paralelo só para
|
|
65
|
+
// provocar o re-render de quem lê `reAuth` do contexto.
|
|
66
|
+
const reAuthRef = useRef(undefined);
|
|
67
|
+
// Handlers estáveis (useCallback) — mesmo padrão dos demais hooks da TUI
|
|
68
|
+
// (ver `../../hooks/use-exit-guard.ts`): identidade fixa evita
|
|
69
|
+
// re-subscrições desnecessárias em quem os usa como dependência de efeito.
|
|
70
|
+
const beginReAuth = useCallback((origin, pending) => {
|
|
71
|
+
const current = reAuthRef.current;
|
|
72
|
+
if (current !== undefined) {
|
|
73
|
+
// Já em andamento (401 concorrente) — apenas acrescenta à lista,
|
|
74
|
+
// mantendo a origem original.
|
|
75
|
+
const next = { origin: current.origin, pending: [...current.pending, pending] };
|
|
76
|
+
reAuthRef.current = next;
|
|
77
|
+
setReAuth(next);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const next = { origin, pending: [pending] };
|
|
81
|
+
reAuthRef.current = next;
|
|
82
|
+
setReAuth(next);
|
|
83
|
+
return true;
|
|
84
|
+
}, []);
|
|
85
|
+
const resolveReAuth = useCallback(() => {
|
|
86
|
+
const current = reAuthRef.current;
|
|
87
|
+
reAuthRef.current = undefined;
|
|
88
|
+
setReAuth(undefined);
|
|
89
|
+
current?.pending.forEach((item) => {
|
|
90
|
+
item.retry();
|
|
91
|
+
});
|
|
92
|
+
}, []);
|
|
93
|
+
const abortReAuth = useCallback((reason) => {
|
|
94
|
+
const current = reAuthRef.current;
|
|
95
|
+
reAuthRef.current = undefined;
|
|
96
|
+
setReAuth(undefined);
|
|
97
|
+
current?.pending.forEach((item) => {
|
|
98
|
+
item.reject(reason);
|
|
99
|
+
});
|
|
100
|
+
}, []);
|
|
101
|
+
const value = {
|
|
102
|
+
url,
|
|
103
|
+
setUrl,
|
|
104
|
+
mode,
|
|
105
|
+
setMode,
|
|
106
|
+
pendingLogin,
|
|
107
|
+
setPendingLogin,
|
|
108
|
+
user,
|
|
109
|
+
setUser,
|
|
110
|
+
callbacks,
|
|
111
|
+
reAuth,
|
|
112
|
+
beginReAuth,
|
|
113
|
+
resolveReAuth,
|
|
114
|
+
abortReAuth,
|
|
115
|
+
};
|
|
116
|
+
return _jsx(OnboardingContext.Provider, { value: value, children: children });
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Hook de onboarding consumido pelas telas `url.tsx`/`mode.tsx`/`login.tsx`/
|
|
120
|
+
* `api-key.tsx`/`validating.tsx`/`success.tsx`.
|
|
121
|
+
*
|
|
122
|
+
* @throws {Error} Se chamado fora de um `<OnboardingProvider>` — indica erro
|
|
123
|
+
* de composição (tela renderizada sem passar pelo roteador).
|
|
124
|
+
*/
|
|
125
|
+
export function useOnboarding() {
|
|
126
|
+
const value = useContext(OnboardingContext);
|
|
127
|
+
if (value === undefined) {
|
|
128
|
+
throw new Error('useOnboarding() usado fora de <OnboardingProvider>.');
|
|
129
|
+
}
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Tela de onboarding: sucesso (#28) — splash comemorativa mostrando o usuário
|
|
4
|
+
* autenticado (`OnboardingContext.user`, preenchido por `validating.tsx`/
|
|
5
|
+
* `api-key.tsx` ao concluir com sucesso).
|
|
6
|
+
*
|
|
7
|
+
* Único lugar da TUI onde um capricho visual mais forte é permitido pelo
|
|
8
|
+
* guideline do tema (`../../theme.tsx`): borda DUPLA + `theme.accent` em
|
|
9
|
+
* destaque, para marcar visualmente a conclusão do fluxo — em qualquer outra
|
|
10
|
+
* tela isso seria decoração em excesso (o guideline pede moderação com
|
|
11
|
+
* `accent`), mas aqui é o clímax de um fluxo que só acontece uma vez por
|
|
12
|
+
* instância configurada.
|
|
13
|
+
*
|
|
14
|
+
* `Enter` segue para a home (M2-06, #29) com a PILHA ZERADA (`resetTo`) —
|
|
15
|
+
* sem telas do onboarding sobrando por baixo para um Esc alcançar. A home é
|
|
16
|
+
* o destino pós-onboarding: lista "minhas issues" com a credencial recém
|
|
17
|
+
* salva.
|
|
18
|
+
*/
|
|
19
|
+
import { Box, Text, useInput } from 'ink';
|
|
20
|
+
import { useCallback, useRef } from 'react';
|
|
21
|
+
import { useNavigation } from '../../navigation.js';
|
|
22
|
+
import { symbols } from '../../symbols.js';
|
|
23
|
+
import { useTheme } from '../../theme.js';
|
|
24
|
+
import { useOnboarding } from './onboarding-context.js';
|
|
25
|
+
/** Tela de onboarding: sucesso — `Enter` segue para a home (pilha zerada via `resetTo`). */
|
|
26
|
+
export function OnboardingSuccessScreen() {
|
|
27
|
+
const theme = useTheme();
|
|
28
|
+
const { resetTo } = useNavigation();
|
|
29
|
+
const { user } = useOnboarding();
|
|
30
|
+
// Handler ESTÁVEL (useCallback + refs, padrão do repo — ver text-input.tsx/app.tsx):
|
|
31
|
+
// identidade nova a cada render des/re-subscreve o useInput e pode perder um Enter rápido.
|
|
32
|
+
const resetToRef = useRef(resetTo);
|
|
33
|
+
resetToRef.current = resetTo;
|
|
34
|
+
const handleInput = useCallback((_input, key) => {
|
|
35
|
+
if (key.return) {
|
|
36
|
+
resetToRef.current('home');
|
|
37
|
+
}
|
|
38
|
+
}, []);
|
|
39
|
+
useInput(handleInput);
|
|
40
|
+
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", paddingX: 3, paddingY: 1, borderStyle: "double", borderColor: theme.accent, children: [_jsxs(Text, { color: theme.accent, children: [symbols.star, " Login conclu\u00EDdo com sucesso! ", symbols.star] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.primary, children: [symbols.tick, " Bem-vindo", user !== undefined ? `, ${user.name}` : '', "!"] }) }), user !== undefined ? (_jsx(Box, { children: _jsxs(Text, { color: theme.muted, children: ["Usu\u00E1rio: ", user.login] }) })) : null, _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: ["Pressione", ' ', _jsx(Text, { color: theme.accent, children: "Enter" }), ' ', "para come\u00E7ar."] }) })] }));
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Resultado da classificação de uma URL digitada (ver {@link classifyUrl}). */
|
|
2
|
+
export type UrlClassification = {
|
|
3
|
+
kind: 'valid';
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'insecure-allowed';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'insecure-blocked';
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'invalid';
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Classifica a URL digitada pelo esquema (`https://`/`http://`) — parsing
|
|
13
|
+
* completo da URL fica para a #28.
|
|
14
|
+
*
|
|
15
|
+
* @param value - Valor bruto do campo (ainda não `trim()`ado).
|
|
16
|
+
* @param insecureEnvSet - `true` quando `REDMINE_INSECURE` está definida no
|
|
17
|
+
* ambiente (ver {@link isInsecureEnvSet}).
|
|
18
|
+
* @example
|
|
19
|
+
* classifyUrl('https://redmine.example', false); // { kind: 'valid' }
|
|
20
|
+
* classifyUrl('http://redmine.example', false); // { kind: 'insecure-blocked' }
|
|
21
|
+
*/
|
|
22
|
+
export declare function classifyUrl(value: string, insecureEnvSet: boolean): UrlClassification;
|
|
23
|
+
/** Tela de onboarding: URL — Enter confirma (avança para `onboarding-mode`), Esc volta (atalho global, ver `../../app.tsx`). */
|
|
24
|
+
export declare function OnboardingUrlScreen(): import("react").JSX.Element;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Tela de onboarding 1/3: URL da instância Redmine (M2-05.1, issue #27).
|
|
4
|
+
*
|
|
5
|
+
* Valida apenas o ESQUEMA da URL ao confirmar (Enter) — a validação real
|
|
6
|
+
* (URL bem formada, instância alcançável) fica para a #28, que chama o core
|
|
7
|
+
* via os callbacks injetáveis do `OnboardingContext`. `http://` só é aceito
|
|
8
|
+
* quando `REDMINE_INSECURE` já está definida no ambiente do processo (`1`
|
|
9
|
+
* ou `true`, case-insensitive — mesma convenção de `../../../mcp/server.ts`
|
|
10
|
+
* e da flag `--insecure` da CLI) — e, mesmo aceito, dispara um aviso em
|
|
11
|
+
* `theme.warning` porque o tráfego (incluindo a api_key futura) não é
|
|
12
|
+
* criptografado.
|
|
13
|
+
*/
|
|
14
|
+
import { Box, Text } from 'ink';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
import { TextInput } from '../../components/text-input.js';
|
|
17
|
+
import { useNavigation } from '../../navigation.js';
|
|
18
|
+
import { symbols } from '../../symbols.js';
|
|
19
|
+
import { useTheme } from '../../theme.js';
|
|
20
|
+
import { useOnboarding } from './onboarding-context.js';
|
|
21
|
+
/**
|
|
22
|
+
* Interpreta `REDMINE_INSECURE` (`1`/`true`, case-insensitive) — mesma
|
|
23
|
+
* convenção usada pelo MCP (`../../../mcp/server.ts`).
|
|
24
|
+
*/
|
|
25
|
+
function isInsecureEnvSet(env) {
|
|
26
|
+
const raw = env.REDMINE_INSECURE;
|
|
27
|
+
return raw !== undefined && /^(1|true)$/i.test(raw.trim());
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Classifica a URL digitada pelo esquema (`https://`/`http://`) — parsing
|
|
31
|
+
* completo da URL fica para a #28.
|
|
32
|
+
*
|
|
33
|
+
* @param value - Valor bruto do campo (ainda não `trim()`ado).
|
|
34
|
+
* @param insecureEnvSet - `true` quando `REDMINE_INSECURE` está definida no
|
|
35
|
+
* ambiente (ver {@link isInsecureEnvSet}).
|
|
36
|
+
* @example
|
|
37
|
+
* classifyUrl('https://redmine.example', false); // { kind: 'valid' }
|
|
38
|
+
* classifyUrl('http://redmine.example', false); // { kind: 'insecure-blocked' }
|
|
39
|
+
*/
|
|
40
|
+
export function classifyUrl(value, insecureEnvSet) {
|
|
41
|
+
const trimmed = value.trim();
|
|
42
|
+
if (trimmed.startsWith('https://') && trimmed.length > 'https://'.length) {
|
|
43
|
+
return { kind: 'valid' };
|
|
44
|
+
}
|
|
45
|
+
if (trimmed.startsWith('http://') && trimmed.length > 'http://'.length) {
|
|
46
|
+
return insecureEnvSet ? { kind: 'insecure-allowed' } : { kind: 'insecure-blocked' };
|
|
47
|
+
}
|
|
48
|
+
return { kind: 'invalid' };
|
|
49
|
+
}
|
|
50
|
+
/** Tela de onboarding: URL — Enter confirma (avança para `onboarding-mode`), Esc volta (atalho global, ver `../../app.tsx`). */
|
|
51
|
+
export function OnboardingUrlScreen() {
|
|
52
|
+
const { push } = useNavigation();
|
|
53
|
+
const theme = useTheme();
|
|
54
|
+
const { url, setUrl, callbacks } = useOnboarding();
|
|
55
|
+
const [feedback, setFeedback] = useState(undefined);
|
|
56
|
+
function handleSubmit(value) {
|
|
57
|
+
const classification = classifyUrl(value, isInsecureEnvSet(process.env));
|
|
58
|
+
const trimmed = value.trim();
|
|
59
|
+
if (classification.kind === 'valid' || classification.kind === 'insecure-allowed') {
|
|
60
|
+
setFeedback(classification.kind === 'insecure-allowed'
|
|
61
|
+
? {
|
|
62
|
+
level: 'warning',
|
|
63
|
+
message: 'AVISO: conexão insegura (http://) habilitada via REDMINE_INSECURE — o tráfego não é criptografado.',
|
|
64
|
+
}
|
|
65
|
+
: undefined);
|
|
66
|
+
setUrl(trimmed);
|
|
67
|
+
callbacks.onUrlSubmit(trimmed);
|
|
68
|
+
push('onboarding-mode');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (classification.kind === 'insecure-blocked') {
|
|
72
|
+
setFeedback({
|
|
73
|
+
level: 'danger',
|
|
74
|
+
message: 'Conexão http:// recusada: TLS é obrigatório. Use https:// ou defina REDMINE_INSECURE (não recomendado) para permitir.',
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
setFeedback({
|
|
79
|
+
level: 'danger',
|
|
80
|
+
message: 'Informe uma URL começando com https:// (ex.: https://redmine.example).',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, borderStyle: "round", borderColor: theme.border, children: [_jsx(Text, { color: theme.primary, children: "URL do Redmine" }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.muted, children: [symbols.pointer, " "] }), _jsx(TextInput, { value: url, onChange: setUrl, onSubmit: handleSubmit, placeholder: "https://redmine.example" })] }), feedback !== undefined ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: feedback.level === 'danger' ? theme.danger : theme.warning, children: [feedback.level === 'danger' ? symbols.cross : symbols.warning, " ", feedback.message] }) })) : 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."] }) })] }));
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function OnboardingValidatingScreen(): import("react").JSX.Element;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Tela de onboarding: validando o login por senha (#28) — consome a
|
|
4
|
+
* `pendingLogin` (promise) guardada em `OnboardingContext` por `login.tsx` ao
|
|
5
|
+
* confirmar o formulário, mostra um spinner enquanto ela resolve, e decide a
|
|
6
|
+
* próxima tela a partir do `OnboardingLoginOutcome`:
|
|
7
|
+
*
|
|
8
|
+
* - `success` → grava `user` no contexto e segue para a splash (`replace`,
|
|
9
|
+
* troca o topo da pilha — a tela de validação é transitória, não faz
|
|
10
|
+
* sentido voltar a ela).
|
|
11
|
+
* - `auth-error` (401/2FA, ADR-003) → oferece o fallback de colar api_key
|
|
12
|
+
* (`replace('onboarding-api-key')`).
|
|
13
|
+
* - `network-error` (rede/TLS) → fica nesta tela mostrando a mensagem em
|
|
14
|
+
* `theme.danger`, sem stack, com a próxima ação sugerida; `Esc` (atalho
|
|
15
|
+
* global, `../../app.tsx`) volta para o formulário de login para tentar de
|
|
16
|
+
* novo.
|
|
17
|
+
*
|
|
18
|
+
* M2-13 (issue #36): quando o sucesso acontece com um fluxo de re-auth em
|
|
19
|
+
* andamento (`OnboardingContext.reAuth`, empilhado por
|
|
20
|
+
* `../../hooks/use-auth-guard.ts` após um 401 numa tela de dados), o caminho
|
|
21
|
+
* de sucesso é DIFERENTE — em vez da splash, a navegação volta à tela de
|
|
22
|
+
* origem (`popTo`) e a operação original é retomada automaticamente
|
|
23
|
+
* (`resolveReAuth()`, que dispara o `retry` guardado no contexto).
|
|
24
|
+
*/
|
|
25
|
+
import { Box, Text } from 'ink';
|
|
26
|
+
import { useEffect, useState } from 'react';
|
|
27
|
+
import { Spinner } from '../../components/spinner.js';
|
|
28
|
+
import { useNavigation } from '../../navigation.js';
|
|
29
|
+
import { symbols } from '../../symbols.js';
|
|
30
|
+
import { useTheme } from '../../theme.js';
|
|
31
|
+
import { useOnboarding } from './onboarding-context.js';
|
|
32
|
+
/** Sugestão de próxima ação para erros de rede/TLS (ex.: CA interna). */
|
|
33
|
+
const NETWORK_ERROR_HINT = 'Se a instância usa uma CA interna, configure NODE_EXTRA_CA_CERTS apontando para o certificado. Verifique também a URL e a conexão de rede.';
|
|
34
|
+
export function OnboardingValidatingScreen() {
|
|
35
|
+
const theme = useTheme();
|
|
36
|
+
const { replace, popTo } = useNavigation();
|
|
37
|
+
const { pendingLogin, setUser, reAuth, resolveReAuth } = useOnboarding();
|
|
38
|
+
const [networkError, setNetworkError] = useState(undefined);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (pendingLogin === undefined) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let cancelled = false;
|
|
44
|
+
pendingLogin
|
|
45
|
+
.then((outcome) => {
|
|
46
|
+
if (cancelled) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (outcome.kind === 'success') {
|
|
50
|
+
setUser(outcome.user);
|
|
51
|
+
if (reAuth !== undefined) {
|
|
52
|
+
// M2-13 (#36): volta à tela de origem e retoma a operação
|
|
53
|
+
// original — não passa pela splash (esse re-login é invisível
|
|
54
|
+
// para o fluxo da tela de dados que o disparou).
|
|
55
|
+
popTo(reAuth.origin);
|
|
56
|
+
resolveReAuth();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
replace('onboarding-success');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (outcome.kind === 'auth-error') {
|
|
63
|
+
replace('onboarding-api-key');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
setNetworkError(outcome.message);
|
|
67
|
+
})
|
|
68
|
+
.catch((cause) => {
|
|
69
|
+
if (cancelled) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Reason: defesa em profundidade — `onLoginSubmit` (ver
|
|
73
|
+
// `../../hooks/use-onboarding-callbacks.ts`) já converte qualquer
|
|
74
|
+
// falha num `OnboardingLoginOutcome` resolvido, mas um callback
|
|
75
|
+
// injetado incorretamente (ex.: em teste) pode rejeitar a promise.
|
|
76
|
+
setNetworkError(cause instanceof Error ? cause.message : String(cause));
|
|
77
|
+
});
|
|
78
|
+
return () => {
|
|
79
|
+
cancelled = true;
|
|
80
|
+
};
|
|
81
|
+
}, [pendingLogin, replace, popTo, reAuth, resolveReAuth, setUser]);
|
|
82
|
+
if (networkError !== undefined) {
|
|
83
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, borderStyle: "round", borderColor: theme.border, children: [_jsxs(Text, { color: theme.danger, children: [symbols.cross, " N\u00E3o foi poss\u00EDvel entrar"] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.danger, children: networkError }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.danger, children: NETWORK_ERROR_HINT }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: ["Pressione", ' ', _jsx(Text, { color: theme.accent, children: "Esc" }), ' ', "para voltar e tentar novamente."] }) })] }));
|
|
84
|
+
}
|
|
85
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, borderStyle: "round", borderColor: theme.border, children: _jsxs(Text, { color: theme.primary, children: [_jsx(Spinner, {}), " Validando credenciais..."] }) }));
|
|
86
|
+
}
|