Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import RouteIntelligencePage from "./components/pages/RouteIntelligencePage";
import CruiseMathPage from "./components/pages/CruiseMathPage";
import TravelInfoPage from "./components/pages/TravelInfoPage";
import KnowledgeDashboardPage from "./components/pages/KnowledgeDashboardPage";
import { ReferenceTripShellPreview } from "./components/pages/TripShellPage";
import SemanticSearchBar from "./semantic-deck/components/SemanticSearchBar";
import { TimoneloSpatialApiClient } from "./semantic-deck/apiClient";
import { SemanticEntity } from "./semantic-deck/types";
Expand Down Expand Up @@ -129,8 +130,7 @@ export default function App() {
)}

{currentRoute === "my-cruise" && (
<CabinDeepDivePage
cabinId="14122"
<ReferenceTripShellPreview
onBack={() => handleNavigate("home")}
/>
)}
Expand Down
51 changes: 51 additions & 0 deletions frontend/src/components/pages/TripShellPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useMemo } from 'react';
import TripShell from '../../trip-shell/TripShell';
import { buildPassengerTripViewModel } from '../../trip-shell/adapter';
import { PassengerTripKnowledgePack, VoyageKnowledgeResult } from '../../trip-shell/types';
import referenceVoyageFixture from '../../fixtures/reference_voyage_bellissima.json';

export interface TripShellPageProps {
pack: PassengerTripKnowledgePack;
result?: VoyageKnowledgeResult;
onBack?: () => void;
previewNotice?: string;
}

/**
* Generic, data-driven Trip Shell Page.
* Strictly consumes an explicit PassengerTripKnowledgePack prop.
*/
export default function TripShellPage({ pack, result, onBack, previewNotice }: TripShellPageProps) {
const viewModel = useMemo(() => {
return buildPassengerTripViewModel(pack, result);
}, [pack, result]);

return (
<div className="w-full flex-1 bg-[#FBF8F3]">
{previewNotice && (
<div className="bg-[#0C1B2A]/5 border-b border-[#0C1B2A]/10 px-4 py-2 text-center text-xs font-mono text-[#5B6570]">
<span>{previewNotice}</span>
</div>
)}
<TripShell viewModel={viewModel} />
</div>
);
}

/**
* Explicit Reference Voyage Preview Wrapper.
* Clearly demarcated as demo/test fixture preview for development and golden testing.
*/
export function ReferenceTripShellPreview({ onBack }: { onBack?: () => void }) {
const pack = referenceVoyageFixture.passenger_pack as unknown as PassengerTripKnowledgePack;
const result = referenceVoyageFixture as unknown as VoyageKnowledgeResult;

return (
<TripShellPage
pack={pack}
result={result}
onBack={onBack}
previewNotice="Reference Voyage Preview • MSC Bellissima (Shanghai → Tokyo)"
/>
);
}
143 changes: 143 additions & 0 deletions frontend/src/fixtures/reference_voyage_bellissima.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{
"voyage_entity": "voyage:msc-bellissima:20261004-shanghai-tokyo",
"input_vessel": "MSC BELLISSIMA",
"input_departure_date": "2026-10-04",
"input_departure_location": "Shanghai, China",
"input_arrival_date": "2026-10-07",
"input_arrival_location": "Tokyo, Japan",
"input_check_in_time": "14:00",
"vessel": "MSC BELLISSIMA",
"departure_port": "port:unlocode:CNSGH",
"arrival_port": "port:unlocode:JPTYO",
"departure_date": "2026-10-04",
"departure_location": "Shanghai, China",
"arrival_date": "2026-10-07",
"arrival_location": "Tokyo, Japan",
"check_in_time": "14:00",
"departure_terminal": null,
"departure_berth": null,
"arrival_terminal": null,
"arrival_berth": null,
"known_facts": [
{
"question_id": "Q-0030",
"value": "MSC BELLISSIMA",
"statement_id": "STM-0403"
},
{
"question_id": "Q-0031",
"value": "2026-10-04",
"statement_id": "STM-0404"
},
{
"question_id": "Q-0032",
"value": "Shanghai, China",
"statement_id": "STM-0405"
},
{
"question_id": "Q-0033",
"value": "port:unlocode:CNSGH",
"statement_id": "STM-0406"
},
{
"question_id": "Q-0034",
"value": "2026-10-07",
"statement_id": "STM-0407"
},
{
"question_id": "Q-0035",
"value": "Tokyo, Japan",
"statement_id": "STM-0408"
},
{
"question_id": "Q-0036",
"value": "port:unlocode:JPTYO",
"statement_id": "STM-0409"
},
{
"question_id": "Q-0037",
"value": "14:00",
"statement_id": "STM-0410"
}
],
"gaps": [
{
"question_id": "Q-0038",
"statement_type": "voyage.departure_terminal",
"status": "UNKNOWN",
"reason": "No authoritative day-specific departure terminal assignment found for sailing date",
"needed_source_class": "port_authority_berth_directory",
"authoritative_source_family": "port_authority",
"recheck_strategy": "Periodic verification of official embarkation port terminal call schedule",
"recheck_window": "30-60 days prior to call",
"last_checked_on": "2026-08-23"
},
{
"question_id": "Q-0039",
"statement_type": "voyage.arrival_terminal",
"status": "UNKNOWN",
"reason": "No authoritative day-specific arrival terminal assignment found for sailing date",
"needed_source_class": "port_authority_berth_directory",
"authoritative_source_family": "port_authority",
"recheck_strategy": "Periodic verification of official debarkation port terminal call schedule",
"recheck_window": "30-60 days prior to call",
"last_checked_on": "2026-08-23"
},
{
"question_id": "Q-0040",
"statement_type": "voyage.departure_berth",
"status": "UNKNOWN",
"reason": "No authoritative day-specific departure berth assignment found for sailing date",
"needed_source_class": "port_authority_berth_directory",
"authoritative_source_family": "port_authority",
"recheck_strategy": "Official port authority daily berth assignment log inspection",
"recheck_window": "30-60 days prior to call",
"last_checked_on": "2026-08-23"
},
{
"question_id": "Q-0041",
"statement_type": "voyage.arrival_berth",
"status": "UNKNOWN",
"reason": "No authoritative day-specific arrival berth assignment found for sailing date",
"needed_source_class": "port_authority_berth_directory",
"authoritative_source_family": "port_authority",
"recheck_strategy": "Official port authority daily berth assignment log inspection",
"recheck_window": "30-60 days prior to call",
"last_checked_on": "2026-08-23"
}
],
"publishability": "PUBLISH_ALLOWED",
"passenger_pack": {
"voyage_entity": "voyage:msc-bellissima:20261004-shanghai-tokyo",
"vessel_name": "MSC BELLISSIMA",
"departure_date": "2026-10-04",
"departure_location": "Shanghai, China",
"departure_port_unlocode": "CNSGH",
"arrival_date": "2026-10-07",
"arrival_location": "Tokyo, Japan",
"arrival_port_unlocode": "JPTYO",
"check_in_time": "14:00",
"departure_terminal_status": "UNCONFIRMED",
"departure_berth_status": "UNCONFIRMED",
"arrival_terminal_status": "UNCONFIRMED",
"arrival_berth_status": "UNCONFIRMED",
"known_generic_infrastructure": [
{
"entity_id": "terminal:JPTYO:tokyo-international-cruise-terminal",
"name": "東京国際クルーズターミナル",
"notice": "Generic port facility in destination port; specific arrival berth assignment is unconfirmed."
}
],
"trust_metadata": {
"governance": "ADR-0002 Truth Engine",
"truth_model": "Fail-Closed Cryptographic Evidence Graph",
"pii_isolation": "Zero PII or raw booking payloads in canonical repository"
},
"next_evidence_gaps": [
"voyage.departure_terminal: No authoritative day-specific departure terminal assignment found for sailing date",
"voyage.arrival_terminal: No authoritative day-specific arrival terminal assignment found for sailing date",
"voyage.departure_berth: No authoritative day-specific departure berth assignment found for sailing date",
"voyage.arrival_berth: No authoritative day-specific arrival berth assignment found for sailing date"
]
}
}
170 changes: 170 additions & 0 deletions frontend/src/trip-shell/TripShell.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { describe, it, expect } from 'vitest';
import { renderToStaticMarkup } from 'react-dom/server';
import TripShell from './TripShell';
import { buildPassengerTripViewModel } from './adapter';
import { PassengerTripKnowledgePack, VoyageKnowledgeResult } from './types';
import referenceVoyageFixture from '../fixtures/reference_voyage_bellissima.json';

const goldenPack = referenceVoyageFixture.passenger_pack as unknown as PassengerTripKnowledgePack;
const goldenResult = referenceVoyageFixture as unknown as VoyageKnowledgeResult;

describe('Passenger Trip Shell v1 Truth-Copy & Boundary Tests (ADR-0002)', () => {
// 1. "All voyage details are verified" is absent
it('does not overclaim that "all voyage details are verified"', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).not.toContain('All voyage details are verified');
expect(html).toContain('Some trip details are confirmed from official sources.');
});

// 2. "Confirmed Voyage" is absent if unresolved gaps exist
it('does not state "Confirmed Voyage" when terminal/berth gaps remain', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).not.toContain('Confirmed Voyage');
expect(html).toContain('Core trip details confirmed');
});

// 3. No automatic-update promise appears
it('does not promise automatic background rechecks or updates', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).not.toContain('update this automatically');
expect(html).not.toContain('will appear here automatically');
expect(html).not.toContain('automatically as official port schedules become available');
expect(html).toContain('Check again closer to departure');
});

// 4. No unsupported 30-60 day passenger claim appears
it('does not emit an unproven 30-60 day port authority timeline claim to passengers', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).not.toContain('30-60 days');
expect(html).not.toContain('30–60 days');
expect(html).not.toContain('typically assign and publish');
});

// 5. check_in_time 14:00 renders exactly as check-in, not recommended/window
it('renders check_in_time 14:00 strictly without window or recommendation upgrade', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).toContain('14:00');
expect(html).not.toContain('Recommended Check-in');
expect(html).not.toContain('confirmed check-in window');
expect(html).not.toContain('Please arrive within your confirmed check-in window');
});

// 6. Terminal/berth remain explicitly unconfirmed
it('renders terminal and berth explicitly as unconfirmed and unassigned', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).toContain('Not confirmed yet');
expect(html).toContain('Not assigned yet');
});

// 7. Generic Tokyo terminal remains clearly non-assigned in Port Information section
it('renders generic destination terminal strictly in separate Port Information section with non-assignment notice', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).toContain('PORT INFORMATION (DESTINATION)');
expect(html).toContain('東京国際クルーズターミナル');
expect(html).toContain('Known port facility — not yet confirmed for your sailing.');
});

// 8. Verified/pending counts do not imply full-trip verification
it('scopes verified and pending counts without claiming complete verification', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).toContain('8 confirmed facts');
expect(html).toContain('4 details pending');
expect(html).not.toContain('Trip fully verified');
expect(html).not.toContain('Fully verified trip');
});

// 9. Primary UI does not expose internal enums
it('strictly insulates internal enums and symbols from visible passenger markup', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).not.toContain('SUPPORTED');
expect(html).not.toContain('PUBLISH_ALLOWED');
expect(html).not.toContain('PUBLISH_BLOCKED');
expect(html).not.toContain('Method.INFERRED');
expect(html).not.toContain('AUTO_ADMISSIBLE');
expect(html).not.toContain('REVIEW_REQUIRED');
expect(html).not.toContain('UNCONFIRMED');
expect(html).not.toContain('UNKNOWN');
expect(html).not.toContain('ART-0007');
});

// 10. PII/trust copy does not overclaim system-wide storage behavior
it('uses precise, architecture-supported PII isolation wording', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
expect(vm.trustSummary.piiNotice).toBe(
'Personal booking details are kept out of the reusable trip knowledge shown here.'
);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).not.toContain('Zero PII or raw booking payloads in canonical repository');
expect(html).not.toContain('Zero PII or raw booking payloads stored');
});

// 11. Golden fixture is clearly reference data
it('derives from the explicit reference voyage fixture', () => {
expect(referenceVoyageFixture.voyage_entity).toBe('voyage:msc-bellissima:20261004-shanghai-tokyo');
expect(goldenPack.vessel_name).toBe('MSC BELLISSIMA');
});

// 12. Production TripShell component remains data-driven
it('renders custom voyage inputs without hardcoded Bellissima dependence', () => {
const customPack: PassengerTripKnowledgePack = {
voyage_entity: 'voyage:custom-ship:20270101-barcelona-genoa',
vessel_name: 'MSC GRANDIOSA',
departure_date: '2027-01-01',
departure_location: 'Barcelona, Spain',
departure_port_unlocode: 'ESBCN',
arrival_date: '2027-01-04',
arrival_location: 'Genoa, Italy',
arrival_port_unlocode: 'ITGOA',
check_in_time: '12:00',
departure_terminal_status: 'UNCONFIRMED',
departure_berth_status: 'UNCONFIRMED',
arrival_terminal_status: 'UNCONFIRMED',
arrival_berth_status: 'UNCONFIRMED',
known_generic_infrastructure: [],
trust_metadata: {
governance: 'ADR-0002 Truth Engine',
truth_model: 'Evidence Graph',
pii_isolation: 'Personal booking details are kept out of the reusable trip knowledge shown here.',
},
next_evidence_gaps: [],
};

const vm = buildPassengerTripViewModel(customPack);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).toContain('MSC Grandiosa');
expect(html).toContain('Barcelona → Genoa');
expect(html).toContain('1–4 Jan 2027');
expect(html).toContain('3 nights');
});

// 13. 320 px and mobile viewports remain usable
it('supports 320px, 375px, 390px responsive viewports without layout breakage', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(
<div style={{ width: '320px' }}>
<TripShell viewModel={vm} />
</div>
);
expect(html).toContain('w-full');
expect(html).toContain('EMBARKATION');
expect(html).toContain('DISEMBARKATION');
});

// 14. Unknown states remain visible, not hidden
it('ensures unknown states are explicitly visible and explained rather than omitted', () => {
const vm = buildPassengerTripViewModel(goldenPack, goldenResult);
const html = renderToStaticMarkup(<TripShell viewModel={vm} />);
expect(html).toContain('Details not available yet');
expect(html).toContain('Departure Terminal &amp; Berth');
expect(html).toContain('Arrival Terminal &amp; Berth');
});
});
Loading