diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index 400a35c3..78733681 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -1,3 +1,13 @@ +## 14/11/2026 — correcção do erro 500 na abertura HTML do dashboard + +Erro confirmado no registo de 14/11 às 10:00 UTC da revisão `73e1d7b9`: `Unable to locate file in Vite manifest: resources/js/pages/management/editorial-workflow.tsx`. O componente de página era também importado estaticamente por outras páginas para reutilizar os estados editoriais. O compilador gerou um chunk partilhado sem a chave de página exigida pelo `@vite` da resposta HTML. O login/302 e os dados JSON podiam funcionar; essas provas não demonstravam a abertura HTML autenticada. + +Correcção delimitada: os mesmos estados passam para `resources/js/lib/editorial-states.ts`; as cinco páginas reutilizam esse módulo, sem importar estaticamente o dashboard. Sem mudança de rotas, permissões, configurações ou dados. Não se alterou o template global nem a segurança para contornar o erro. + +Regressão reproduzida com o manifesto anterior. Com o build corrigido, o teste `EditorialDashboardBuildTest` usa o manifesto real e abre as duas páginas HTML autenticadas (visão geral e legislação), sem `withoutVite`: **1/8**. Os testes frontend afectados e o novo controlo de manifesto passaram (**12 casos**), com tipos/build aprovados. O controlo `editorial-dashboard-manifest.test.mjs` corre depois da compilação na bateria frontend obrigatória e impede promover uma candidata sem a entrada de página. Provas em `outputs/editorial-dashboard-500-20261114`. + +Estado desta passagem: correcção em QA para reparar a entrega autorizada; instalação e conferência do build servido em curso pelo executor Git existente, com novo backup/restauro. Não declarar o 500 resolvido antes de conferir o manifesto e a geração HTML da revisão instalada. Conferência visual continua separada e pendente pelo conector indisponível. + ## 14/11/2026 — menu Administração/Dashboard e apresentação instalados em 73e1d7b9 **Entrega autorizada concluída:** `73e1d7b96419873c6924e04e94f2a7374c18eb87`, confirmada em `https://teste-investidor.tempo.co.mz/build/revision.json`. Administração fica no menu principal para contas com destinos administrativos autorizados; Dashboard abre o submenu das actividades. A página `/management/editorial` apresenta os três grupos de cartões descritos abaixo, com fila e notificações preservadas. O painel operacional anterior, quando autorizado, fica também dentro de Dashboard. Visitantes mantêm os acessos públicos; não houve concessão de permissões. diff --git a/resources/js/lib/editorial-states.ts b/resources/js/lib/editorial-states.ts new file mode 100644 index 00000000..e1cd4f7e --- /dev/null +++ b/resources/js/lib/editorial-states.ts @@ -0,0 +1,10 @@ +export const states: Record = { + draft: 'Rascunho', + submitted: 'Submetido', + in_review: 'Em revisão', + approved: 'Aprovado', + published: 'Publicado', + returned: 'Devolvido', + rejected: 'Rejeitado', + archived: 'Arquivado', +}; diff --git a/resources/js/pages/management/editorial-edition.tsx b/resources/js/pages/management/editorial-edition.tsx index 4a4ce0ee..f2245b1d 100644 --- a/resources/js/pages/management/editorial-edition.tsx +++ b/resources/js/pages/management/editorial-edition.tsx @@ -1,6 +1,6 @@ import { Link, useForm } from '@inertiajs/react'; import { Management, Field, button, control } from './shared'; -import { states } from './editorial-workflow'; +import { states } from '@/lib/editorial-states'; import { labels as portfolioLabels } from '../portfolio/shared'; import SubjectForm from './editorial-subject-form'; import type { SubjectField } from './editorial-subject-form'; diff --git a/resources/js/pages/management/editorial-subjects.tsx b/resources/js/pages/management/editorial-subjects.tsx index e7b63e8b..3c41b9c7 100644 --- a/resources/js/pages/management/editorial-subjects.tsx +++ b/resources/js/pages/management/editorial-subjects.tsx @@ -1,6 +1,6 @@ import { Link } from '@inertiajs/react'; import { Management } from './shared'; -import { states } from './editorial-workflow'; +import { states } from '@/lib/editorial-states'; const titles: Record = { projects: 'Projectos', diff --git a/resources/js/pages/management/editorial-workflow.tsx b/resources/js/pages/management/editorial-workflow.tsx index 5d8afa86..37a9bb7f 100644 --- a/resources/js/pages/management/editorial-workflow.tsx +++ b/resources/js/pages/management/editorial-workflow.tsx @@ -1,16 +1,8 @@ import { Link, router } from '@inertiajs/react'; import { Field, Management, button, control } from './shared'; -export const states: Record = { - draft: 'Rascunho', - submitted: 'Submetido', - in_review: 'Em revisão', - approved: 'Aprovado', - published: 'Publicado', - returned: 'Devolvido', - rejected: 'Rejeitado', - archived: 'Arquivado', -}; +import { states } from '@/lib/editorial-states'; + type Item = { id: number; title: string; diff --git a/resources/js/pages/structures/competitiveness.tsx b/resources/js/pages/structures/competitiveness.tsx index a5ee9a0d..cf33d346 100644 --- a/resources/js/pages/structures/competitiveness.tsx +++ b/resources/js/pages/structures/competitiveness.tsx @@ -14,7 +14,7 @@ import type { Force, } from '@/lib/cluster-competitiveness'; import { button, control } from '../management/shared'; -import { states as editorialStates } from '../management/editorial-workflow'; +import { states as editorialStates } from '@/lib/editorial-states'; const labels = { evidence: 'Observações e evidências locais', diff --git a/resources/js/pages/structures/index.tsx b/resources/js/pages/structures/index.tsx index 193e520f..2a8fa72e 100644 --- a/resources/js/pages/structures/index.tsx +++ b/resources/js/pages/structures/index.tsx @@ -9,7 +9,7 @@ import { import type { Paginated } from '../management/shared'; import { titles, statuses } from './types'; import type { Structure, StructureRecord } from './types'; -import { states as editorialStates } from '../management/editorial-workflow'; +import { states as editorialStates } from '@/lib/editorial-states'; export default function Index({ structure, diff --git a/tests/Feature/EditorialDashboardBuildTest.php b/tests/Feature/EditorialDashboardBuildTest.php new file mode 100644 index 00000000..f7e21a91 --- /dev/null +++ b/tests/Feature/EditorialDashboardBuildTest.php @@ -0,0 +1,32 @@ +assertTrue(IsolatedQaDatabase::allowsSchemaChanges()); + $directory = getenv('EDITORIAL_QA_BUILD_DIRECTORY') ?: 'build'; + $manifest = json_decode(file_get_contents(public_path($directory.'/manifest.json')), true, 512, JSON_THROW_ON_ERROR); + $key = 'resources/js/pages/management/editorial-workflow.tsx'; + $this->assertArrayHasKey($key, $manifest, 'The authenticated dashboard must be a compiled page entry, not only a shared chunk.'); + $this->app->make(Vite::class)->useBuildDirectory($directory)->useHotFile(storage_path('phpunit-no-vite-hot')); + config(['portal.qa_build_directory' => $directory, 'editorial_workflow.enabled' => true, 'portal.profile_access_enabled' => false]); + $actor = User::factory()->create(); + config(['portal.managers' => [$actor->email]]); + $this->actingAsWithMfa($actor); + foreach (['/management/editorial', '/management/editorial?kind=legislation'] as $url) { + $this->get($url, ['Accept' => 'text/html'])->assertOk()->assertSee('data-page=', false)->assertSee($manifest[$key]['file'], false); + } + } +} diff --git a/tests/frontend/cluster-editorial.test.mjs b/tests/frontend/cluster-editorial.test.mjs index a1c5e741..1310822d 100644 --- a/tests/frontend/cluster-editorial.test.mjs +++ b/tests/frontend/cluster-editorial.test.mjs @@ -34,7 +34,7 @@ const Page = load('../../resources/js/pages/structures/competitiveness.tsx', { PortalApiError: Error, }, '../management/shared': { button: '', control: '' }, - '../management/editorial-workflow': { + '@/lib/editorial-states': { states: { draft: 'Rascunho', approved: 'Aprovado', diff --git a/tests/frontend/editorial-dashboard-manifest.test.mjs b/tests/frontend/editorial-dashboard-manifest.test.mjs new file mode 100644 index 00000000..9b7a6cdb --- /dev/null +++ b/tests/frontend/editorial-dashboard-manifest.test.mjs @@ -0,0 +1,13 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +test('compiled dashboard remains a resolvable page entry for authenticated full HTML', () => { + const manifestPath=process.env.EDITORIAL_BUILD_MANIFEST || fileURLToPath(new URL('../../public/build/manifest.json',import.meta.url)); + const manifest=JSON.parse(fs.readFileSync(manifestPath,'utf8')); + const key='resources/js/pages/management/editorial-workflow.tsx'; + assert.ok(manifest[key], 'Dashboard page entry missing; a shared chunk cannot satisfy the server-rendered Vite directive'); + assert.equal(manifest[key].isDynamicEntry || manifest[key].isEntry, true); + assert.ok(fs.existsSync(path.join(path.dirname(manifestPath),manifest[key].file))); +}); diff --git a/tests/frontend/editorial-dashboard-presentation.test.mjs b/tests/frontend/editorial-dashboard-presentation.test.mjs index 07c64ffa..e85185fc 100644 --- a/tests/frontend/editorial-dashboard-presentation.test.mjs +++ b/tests/frontend/editorial-dashboard-presentation.test.mjs @@ -7,7 +7,7 @@ import {renderToStaticMarkup} from 'react-dom/server'; import ts from 'typescript'; const require=createRequire(import.meta.url), module={exports:{}}; const code=ts.transpileModule(fs.readFileSync(new URL('../../resources/js/pages/management/editorial-workflow.tsx',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,jsx:ts.JsxEmit.ReactJSX}}).outputText; -new Function('require','module','exports',code)(name=>name==='@inertiajs/react'?{Link:({href,children,...props})=>React.createElement('a',{href,...props},children),router:{}}:name==='./shared'?{Management:({title,children})=>React.createElement('main',null,React.createElement('h1',null,title),children),Field:({label,children})=>React.createElement('label',null,label,children),button:'',control:''}:require(name),module,module.exports); +new Function('require','module','exports',code)(name=>name==='@/lib/editorial-states'?{states:{}}:name==='@inertiajs/react'?{Link:({href,children,...props})=>React.createElement('a',{href,...props},children),router:{}}:name==='./shared'?{Management:({title,children})=>React.createElement('main',null,React.createElement('h1',null,title),children),Field:({label,children})=>React.createElement('label',null,label,children),button:'',control:''}:require(name),module,module.exports); const base={items:{data:[],current_page:1,last_page:1},notices:[],resourceAccess:true,opportunitiesUrl:'/opportunities-qa',opportunityPending:2,clustersUrl:'/clusters-qa',nativePending:{clusters:3,territories:0,zones:0},subjectsUrl:'/subjects-qa',territorialUrl:'/territories-qa',demographyUrl:'/demography-qa'}; test('dashboard groups authorised activities once with contextual counts and an empty notification state',()=>{ const html=renderToStaticMarkup(React.createElement(module.exports.default,base)); diff --git a/tests/frontend/editorial-edition.test.mjs b/tests/frontend/editorial-edition.test.mjs index 4abbc3fc..75d92ee7 100644 --- a/tests/frontend/editorial-edition.test.mjs +++ b/tests/frontend/editorial-edition.test.mjs @@ -49,7 +49,7 @@ new Function('require', 'module', 'exports', code)( control: '', button: '', }; - if (name === './editorial-workflow') + if (name === '@/lib/editorial-states') return { states: { draft: 'Rascunho',