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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public/assets/ui-foundation-*.js
public/assets/viewer-*.js
public/assets/viewer-*.css
public/assets/dashboard-*.js
public/assets/marketing-video-*.js
public/assets/manifest.json

# Runtime state / secrets
Expand Down
17 changes: 17 additions & 0 deletions BUILD-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -890,3 +890,20 @@ Library navigation uses native links, styled as tabs, with `library=mine|builtin
Scoped integration red controls fail on the previous source for missing detail groups, missing library selection and visible card descriptions. Existing assertions move to the explicit personal destination / new query-preserving preview URLs. Browser regressions exercise the existing seven responsive projects, real sandboxed owner preview and JavaScript-disabled library navigation. The style guide documents these two targeted patterns using current primitives; no global redesign, dependency, schema, provider or launch-flag change is included.

Homepage activation is separately authorized after this UI rollout is verified: existing `AA_COMING_SOON=false` selects the already-built HomePage. This PR does not merge/deploy or change that runtime flag. Existing production deployment and rollback safeguards remain required.


## Homepage launch video — deliberately activated, current marketing system

The approved scope is one centered 4:3, maximum780px film after the existing hero/setup and before Examples. `MarketingVideo` is registered in the style guide and uses current primitives/tokens. Existing hero/CTAs/setup, icons, admin/detail/red analytics, public gallery and all20 canonical template/source/thumbnail options remain unchanged. This is not adoption of the rejected broad admin proposal or the unapproved icon alternatives.

The progressive Watch link becomes a native button only when its small page-specific script runs. The server emits no movie/source/track src; scroll, hover and focus are passive. Explicit activation pins one phone/desktop rendition by actual CSS width (≤480px phone, not DPR); native controls, focus, playsinline, retry/open-file and no-JavaScript navigation remain usable. No autoplay, loop, HLS, external player or service. Responsive eager posters reserve geometry and avoid a lazy LCP candidate; high-DPR phones may select the34,330-byte still without loading both posters.

Four exact frozen media deliveries total7,014,717bytes; movies are46s/60fps (phone2,843,251bytes, desktop4,122,608bytes), posters14,528/34,330bytes. No master, alternate encodes or production review pages enter the repository. One840-byte authored WebVTT visual alternative follows the same factual sequence as the native transcript; it does not invent speech/sound captions. See [homepage-video.md](docs/homepage-video.md) for full hashes, provenance and reproduction options.

Existing static serving already supports real byte ranges. Only successful content-hashed MP4/WebP/VTT paths gain immutable caching, MP4 HEAD/full responses advertise ranges, and VTT receives its required MIME instead of the adapter's octet-stream fallback. Actual TCP full/range/suffix/HEAD readback and native track parsing protect this behavior. No Nginx, environment, launch flag, CSP, sandbox, provider, database or dependency change is required.

Scoped tests include genuine before-fail component/cache controls; no passive movie load; one rendition; native keyboard timeline/focus; aborted-network and rejected-play retries; progressive no-JS; complete media hashes/faststart and actual five-cue parsing. Final local check/typecheck/build and full test suites are required, with41 pre-existing warnings and existing skips explicitly distinguished. Responsive main1440/768/390/320 and actual width-policy/gutter boundaries, eight matched existing-page screenshot pairs and actual network-emulated phone playback supplement the suite.

The five-run cold-phone lab uses real loopback HTTP at configured1.6Mbps/150ms latency, not request substitution. Performance is lab evidence, not a physical-phone/deployed-CDN guarantee. Reserved player activation geometry/CLS is separate from initial font swaps in the unchanged page shell. Only Chromium is verified; no perceptual audio audition or Safari/Firefox certification is claimed. Parent must review the exact PR before the existing guarded deployment; homepage remains live and untouched until that separately authorized code rollout.

Pre-review local receipts:1,153 unit/integration tests and338 E2E tests pass, with12 existing skips in each suite and41 exact baseline warning locations/rules. Thirteen clean responsive/boundary cells show zero passive movie requests, one poster/one selected movie and zero player-activation CLS. Five cold-phone full-length trials measured click→first presented frame705.2/697.8/692.4/696.2/696.3ms (median696.3ms, worst705.2ms), no post-first-frame stalls; the first run dropped8 of2,760 frames on the shared lab host, others0. These timings precede only a server-side VTT MIME correction; movie/player/CSS/markup bytes remained equal. Final actual HTTP/native-cue tests verify that correction separately. Eight hero/header/legal/catalog before/after PNG pairs are byte-identical at1440/390 with the same synthetic visible base URL. A separate desktop full-duration/native-volume/mute/fullscreen-entry/exit check passed. No claim of a live deployment is made here.
57 changes: 57 additions & 0 deletions docs/homepage-video.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Homepage launch film

## Scope and activation contract

`MarketingVideo` is registered in the existing style guide and inserted after the cloud homepage's hero/setup, before Examples. It uses current tokens, a centered maximum 780px frame and a reserved 4:3 ratio. No hero/CTA/icon, admin, canonical template, authentication, billing, CSP, launch-flag, Nginx or deployment-script change accompanies it.

The server emits **no video/source/track src**. The default Watch action is an ordinary link to the optimized phone MP4; the small page-specific script replaces that link with a native button. Idle, scroll, hover and focus never attach movie URLs. Explicit activation selects one rendition using the actual frame CSS width: at most 480px gets phone, wider gets desktop. The choice remains pinned across resize/fullscreen/retry. High DPR intentionally does not force a heavier phone movie; this is a transfer-quality tradeoff, not a pixel-for-pixel retina promise.

The image is a responsive picture, not a second video poster request. Its `sizes` reflect existing 16/24px gutters, and high-DPR phones may choose the 960px still (34,330 bytes, still below the 40KB phone target). Only one still is requested. It is eager because actual opening viewport LCP probes identified it as an LCP candidate. Width/height and aspect ratio reserve geometry.

Native controls, playsinline, unmuted audio after activation, focus transfer, pause/seek/fullscreen/volume and a visible retry/open-file path are retained. A rejected play promise or failed request exposes a focused retry; no automatic second movie downloads. A pending 15-second deadline offers retry, rather than promising that every connection will load. The action/status area reserves space without changing the action label. No autoplay, loop, player library, HLS, prefetch or third-party request is introduced.

## Exact media provenance

Selected master: `agent-artifacts-v5-new-master.mp4`, SHA-256 `306f87df1bec707d4027d395db10f7ec4ee6421d6aa3f17ca641df90ad1983bb`, 1440×1080, 60fps, 46 seconds. The original master/compact/audio and production render directories are not included in this repository change.

Frozen media bundle: `74b35dc950f75030c6722e929f97cd3997a879340885f19a2cf9968373260e7e`. The first four files below are exact media-worker delivery bytes. The fifth is an authored, 840-byte timed visual alternative derived from that delivery's factual visual transcript; it is not a re-encoded movie, narration, or invented sound captions.

| File in `public/assets/media/` | Bytes | Full SHA-256 |
|---|---:|---|
| `agent-artifacts-demo-desktop.eccb5261ce8a945e.mp4` | 4,122,608 | `eccb5261ce8a945ef779ecb2575241ddd9f7d615cddce7cb99a2c6451df14cd1` |
| `agent-artifacts-demo-phone.debfd582ad844195.mp4` | 2,843,251 | `debfd582ad844195d4af3a7cb8321f563edf16c23afc457588785066f0f75075` |
| `agent-artifacts-demo-poster-480.83c10656873f7de8.webp` | 14,528 | `83c10656873f7de8c2f3b52609f8c23d51c7c63557a0b4b144f30db297d2be88` |
| `agent-artifacts-demo-poster-960.13ccf9f3467fcdd8.webp` | 34,330 | `13ccf9f3467fcdd8177563613b8771e854ccc70852208bf5ff0e1691979979bb` |
| `agent-artifacts-demo-transcript.89c7595fab39d9ee.vtt` | 840 | `89c7595fab39d9ee48d8f8171e25510fd73f7af236a51b4760f0cc288bc5934f` |

Both movies are H.264 High / yuv420p, 60fps, 46s, 2,760 frames, with a single AAC stereo 48kHz encode of the selected lossless new mix. Desktop: 960×720, direct-master CRF22/slow, maximum keyframe gap 2s. Phone: 720×540, exact original compact picture-packet copy, maximum keyframe gap approximately 4.167s. Both are faststart (`moov` before `mdat`). The 30fps desktop trial saved only 4.6%; retaining finer motion was preferred. The phone picture-copy was smaller than a fresh 30fps encode, without an extra picture generation.

The 6.9-second posters retain the complete “From a reply. To a real page.” frame. No new crop, image generation or film encoding was performed by the product integrator.

### Reproduction recipe (archival inputs, not part of build)

The media worker used FFmpeg; the commands below preserve its selected options. Supply the selected original files as `master.mp4`, `compact.mp4` and `new-mix.wav` in a disposable working directory. Do not replace originals. Exact delivery hashes, rather than cross-version encoder determinism, are the adoption gate.

```sh
ffmpeg -i new-mix.wav -map 0:a:0 -c:a aac -b:a 128k -ar 48000 -ac 2 -t 46 -movflags +faststart audio128.m4a
ffmpeg -i master.mp4 -i audio128.m4a -map 0:v:0 -map 1:a:0 -vf 'scale=960:720:flags=lanczos:in_range=tv:out_range=tv,fps=60,setsar=1' -c:v libx264 -preset slow -crf 22 -threads 4 -pix_fmt yuv420p -profile:v high -g 120 -color_range tv -colorspace smpte170m -color_primaries bt709 -color_trc bt709 -c:a copy -t 46 -movflags +faststart desktop.mp4
ffmpeg -i compact.mp4 -i audio128.m4a -map 0:v:0 -map 1:a:0 -c copy -t 46 -movflags +faststart phone.mp4
ffmpeg -ss 6.9 -i master.mp4 -frames:v 1 -vf 'scale=480:360:flags=lanczos' -c:v libwebp -lossless 0 -quality 86 -compression_level 6 poster480.webp
ffmpeg -ss 6.9 -i master.mp4 -frames:v 1 -vf 'scale=960:720:flags=lanczos' -c:v libwebp -lossless 0 -quality 86 -compression_level 6 poster960.webp
```

The optional native “Visual transcript (English)” track and the always-available native details describe five visual sequences. They explicitly do not transcribe speech. The source production notes describe music/SFX, not narration. Objective audio decoding/alignment/no-clipping checks were supplied, but **no perceptual audio audition is claimed**.

## Static HTTP contract

Files follow the existing same-origin `/assets/` convention and are included by the existing package/Docker copy. No new static service or Nginx alias is needed: the existing static adapter provides real byte-range streams. Successful content-hashed MP4/WebP/VTT responses alone get one-year immutable caching. MP4 full/HEAD responses advertise byte ranges; partial responses retain exact 206 Content-Range/length. VTT receives `text/vtt; charset=utf-8` (the adapter's default octet-stream is not adequate for a native track). Missing/416 responses and unhashed files do not receive immutable caching. Homepage cache policy and CSP/sandbox policies remain unchanged.

Only selected delivery files are committed. Generated CSS/JS filenames and manifest remain the existing build's responsibility. A content change must create a new media hash/path; never overwrite an immutable URL.

## Regression and rollout boundaries

Scoped unit/integration/browser tests cover passive absence, progressive links, one rendition, failure/policy rejection with retry, native focus/keyboard timeline, exact hashes/faststart, WebVTT parsing, and HTTP range/cache/MIME positive and negative cases. Full existing suites remain required.

Local throughput checks use actual loopback HTTP with browser network emulation, not request fulfillment. They are reproducible lab measurements, not deployed-CDN or physical-device promises. Chromium is the verified engine; Safari/iOS/Firefox and perceptual audio listening remain unverified. Reserved-frame activation CLS is measured separately from initial font swaps in the unchanged page shell; do not claim global page CLS is zero.

Deployment is a separately reviewed operation using the existing guarded production script. Before enabling page references, verify all five media files and hashes exist in the checked-out static root; the existing checkout-before-build-before-reload order provides this ordering. Verify public full hashes, both real ranges, WebVTT MIME, built script/CSS identity, and anonymous click-to-play behavior afterward. Keep the existing live homepage flag unchanged. Rollback is an auditable scoped revert plus the same guarded deploy, not an environment flip or routine database restore. Preserve old content-hashed files while previously served references can remain cached.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
WEBVTT

NOTE Descriptive visual transcript; no spoken dialogue or unverified sound effects are transcribed.

00:00:00.000 --> 00:00:08.000
An agent creates a private weekly growth report.
The reply opens as a formatted page with a chart and a recommended next step.

00:00:08.000 --> 00:00:17.000
The owner creates and shares a link, then adds password protection.
A recipient enters the password to open the report.

00:00:17.000 --> 00:00:28.000
The person asks the agent for fresh numbers in the same conversation.
The report updates at the same link. Earlier versions remain available.

00:00:28.000 --> 00:00:35.000
Reusable templates, saving a customized report as a template,
and Markdown and HTML presentation are shown.

00:00:35.000 --> 00:00:46.000
Several agents connect to one artifact link.
“Publish your agent’s work.”
5 changes: 5 additions & 0 deletions scripts/build-assets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ const ASSETS = [
{ key: 'ui-foundation.js', source: 'src/ui/client/ui-foundation.js', name: 'ui-foundation' },
{ key: 'viewer.js', source: 'src/ui/client/viewer.js', name: 'viewer' },
{ key: 'dashboard.js', source: 'src/ui/client/dashboard.js', name: 'dashboard' },
{
key: 'marketing-video.js',
source: 'src/ui/client/marketing-video.js',
name: 'marketing-video',
},
{ key: 'viewer.css', source: 'src/ui/assets/viewer.css', name: 'viewer' },
];

Expand Down
15 changes: 14 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { createWebRoute } from './routes/web.js';
import type { AnalyticsRecorder } from './services/analytics.js';
import { SESSION_COOKIE_NAME } from './services/sessions.js';
import { analyticsTag, configureAnalytics } from './ui/analytics.js';
import { isHashedAssetPath } from './ui/assets.js';
import { isHashedAssetPath, isHashedMediaPath } from './ui/assets.js';
import { ErrorPage } from './ui/pages/error-page.js';

interface AppVariables {
Expand Down Expand Up @@ -118,6 +118,19 @@ export function createApp({
// and a year of immutable caching on those would strand the old copy in every CDN and browser.
app.use('/assets/*', async (context, next) => {
await next();
// serveStatic already supplies real byte-range streams. Cache successful hashed media ranges
// too, and advertise range support on full/HEAD responses; no homepage or CSP policy change.
const media = isHashedMediaPath(context.req.path);
if (media && [200, 206, 304].includes(context.res.status)) {
context.header('Cache-Control', 'public, max-age=31536000, immutable');
if (context.req.path.endsWith('.mp4')) {
context.header('Accept-Ranges', 'bytes');
}
if (context.req.path.endsWith('.vtt')) {
// The static adapter otherwise falls back to octet-stream; tracks require WebVTT MIME.
context.header('Content-Type', 'text/vtt; charset=utf-8');
}
}
if (isHashedAssetPath(context.req.path) && [200, 304].includes(context.res.status)) {
context.header('Cache-Control', 'public, max-age=31536000, immutable');
}
Expand Down
13 changes: 12 additions & 1 deletion src/ui/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ const BUILD_COMMAND = 'pnpm run build:assets';
* Every hashed asset a page may ask for. The names match the keys `scripts/build-assets.mjs`
* writes, and the union is what stops a page naming an asset the build does not produce.
*/
export type AssetKey = 'app.css' | 'ui-foundation.js' | 'viewer.js' | 'dashboard.js' | 'viewer.css';
export type AssetKey =
| 'app.css'
| 'ui-foundation.js'
| 'viewer.js'
| 'dashboard.js'
| 'viewer.css'
| 'marketing-video.js';

/** Mirrors the static root `src/app.ts` serves `/assets/*` from. Both resolve from the install. */
const servedPublicRoot = (): string => appPath('public');
Expand All @@ -42,6 +48,11 @@ export function isHashedAssetPath(path: string): boolean {
return HASHED_ASSET_PATTERN.test(path);
}

/** Selected, content-addressed marketing media only; never a blanket /assets cache policy. */
export function isHashedMediaPath(path: string): boolean {
return /^\/assets\/media\/[a-z0-9-]+\.[a-f0-9]{16}\.(?:mp4|webp|vtt)$/.test(path);
}

export interface AssetResolverOptions {
/** Directories that may hold `manifest.json`, in priority order. */
assetRoots: readonly string[];
Expand Down
73 changes: 73 additions & 0 deletions src/ui/assets/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -1861,6 +1861,79 @@
line-height: 1;
}

/* The film owns its own reserved geometry. No change to hero, page shell or existing sections. */
.aa-marketing-video {
width: min(100%, var(--width-aa-panel));
margin: var(--spacing-aa-12) auto 0;
}

.aa-marketing-video__frame {
position: relative;
aspect-ratio: 4 / 3;
overflow: hidden;
border-radius: var(--radius-aa-lg);
background: var(--color-aa-surface-raised);
box-shadow: var(--shadow-aa-card);
}

.aa-marketing-video__frame picture,
.aa-marketing-video__frame img,
.aa-marketing-video__frame video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain;
}

.aa-marketing-video__frame picture {
z-index: 1;
pointer-events: none;
}

.aa-marketing-video__frame video:focus-visible {
outline: 3px solid var(--color-aa-accent);
outline-offset: -3px;
}

.aa-marketing-video__actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-aa-3) var(--spacing-aa-4);
margin-top: var(--spacing-aa-4);
}

.aa-marketing-video__status {
min-height: 3em;
margin: var(--spacing-aa-2) 0;
color: var(--color-aa-muted);
font-size: var(--text-aa-sm);
line-height: 1.5;
}

.aa-marketing-video__transcript summary {
width: fit-content;
cursor: pointer;
color: var(--color-aa-muted);
}

.aa-marketing-video__transcript-body {
display: grid;
gap: var(--spacing-aa-3);
margin-top: var(--spacing-aa-4);
line-height: 1.6;
}

.aa-marketing-video__transcript-body ol {
padding-left: var(--spacing-aa-6);
list-style: decimal;
}

.aa-marketing-video__transcript-body :where(li + li) {
margin-top: var(--spacing-aa-2);
}

.aa-marketing-section {
display: grid;
justify-items: center;
Expand Down
Loading
Loading