Fix authenticated editorial dashboard missing Vite page entry
tests / ci (push) Waiting to run

This commit is contained in:
2026-11-14 12:04:52 +02:00
parent 203c8f9193
commit dd20e74703
12 changed files with 74 additions and 17 deletions
@@ -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 ## 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. **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.
+10
View File
@@ -0,0 +1,10 @@
export const states: Record<string, string> = {
draft: 'Rascunho',
submitted: 'Submetido',
in_review: 'Em revisão',
approved: 'Aprovado',
published: 'Publicado',
returned: 'Devolvido',
rejected: 'Rejeitado',
archived: 'Arquivado',
};
@@ -1,6 +1,6 @@
import { Link, useForm } from '@inertiajs/react'; import { Link, useForm } from '@inertiajs/react';
import { Management, Field, button, control } from './shared'; 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 { labels as portfolioLabels } from '../portfolio/shared';
import SubjectForm from './editorial-subject-form'; import SubjectForm from './editorial-subject-form';
import type { SubjectField } from './editorial-subject-form'; import type { SubjectField } from './editorial-subject-form';
@@ -1,6 +1,6 @@
import { Link } from '@inertiajs/react'; import { Link } from '@inertiajs/react';
import { Management } from './shared'; import { Management } from './shared';
import { states } from './editorial-workflow'; import { states } from '@/lib/editorial-states';
const titles: Record<string, string> = { const titles: Record<string, string> = {
projects: 'Projectos', projects: 'Projectos',
@@ -1,16 +1,8 @@
import { Link, router } from '@inertiajs/react'; import { Link, router } from '@inertiajs/react';
import { Field, Management, button, control } from './shared'; import { Field, Management, button, control } from './shared';
export const states: Record<string, string> = { import { states } from '@/lib/editorial-states';
draft: 'Rascunho',
submitted: 'Submetido',
in_review: 'Em revisão',
approved: 'Aprovado',
published: 'Publicado',
returned: 'Devolvido',
rejected: 'Rejeitado',
archived: 'Arquivado',
};
type Item = { type Item = {
id: number; id: number;
title: string; title: string;
@@ -14,7 +14,7 @@ import type {
Force, Force,
} from '@/lib/cluster-competitiveness'; } from '@/lib/cluster-competitiveness';
import { button, control } from '../management/shared'; import { button, control } from '../management/shared';
import { states as editorialStates } from '../management/editorial-workflow'; import { states as editorialStates } from '@/lib/editorial-states';
const labels = { const labels = {
evidence: 'Observações e evidências locais', evidence: 'Observações e evidências locais',
+1 -1
View File
@@ -9,7 +9,7 @@ import {
import type { Paginated } from '../management/shared'; import type { Paginated } from '../management/shared';
import { titles, statuses } from './types'; import { titles, statuses } from './types';
import type { Structure, StructureRecord } 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({ export default function Index({
structure, structure,
@@ -0,0 +1,32 @@
<?php
namespace Tests\Feature;
use App\Models\User;
use App\Support\IsolatedQaDatabase;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Vite;
use Tests\Support\AuthenticatesQaActors;
use Tests\TestCase;
final class EditorialDashboardBuildTest extends TestCase
{
use AuthenticatesQaActors, DatabaseTransactions;
public function test_authenticated_full_html_opens_with_the_real_compiled_dashboard_entry(): void
{
$this->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);
}
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ const Page = load('../../resources/js/pages/structures/competitiveness.tsx', {
PortalApiError: Error, PortalApiError: Error,
}, },
'../management/shared': { button: '', control: '' }, '../management/shared': { button: '', control: '' },
'../management/editorial-workflow': { '@/lib/editorial-states': {
states: { states: {
draft: 'Rascunho', draft: 'Rascunho',
approved: 'Aprovado', approved: 'Aprovado',
@@ -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)));
});
@@ -7,7 +7,7 @@ import {renderToStaticMarkup} from 'react-dom/server';
import ts from 'typescript'; import ts from 'typescript';
const require=createRequire(import.meta.url), module={exports:{}}; 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; 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'}; 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',()=>{ test('dashboard groups authorised activities once with contextual counts and an empty notification state',()=>{
const html=renderToStaticMarkup(React.createElement(module.exports.default,base)); const html=renderToStaticMarkup(React.createElement(module.exports.default,base));
+1 -1
View File
@@ -49,7 +49,7 @@ new Function('require', 'module', 'exports', code)(
control: '', control: '',
button: '', button: '',
}; };
if (name === './editorial-workflow') if (name === '@/lib/editorial-states')
return { return {
states: { states: {
draft: 'Rascunho', draft: 'Rascunho',