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
7 changes: 4 additions & 3 deletions src/interactive/sharePrompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,17 @@ describe('runSharePrompt', () => {
expect(handle.analytics.capturedEvents('share_choice')[0]?.properties).toMatchObject({ choice: 'declined' });
});

test('html-only: uploads the raw html bytes with kind:html', async () => {
test('html-only: uploads the raw html bytes with owner and email', async () => {
const handle = fakeContextHandle.build();
handle.prompter.scriptSelect('html').scriptText('ben@example.com');

const outcome = await runSharePrompt(sharePromptInputsFor(handle));

expect(outcome).toMatchObject({ kind: 'shared', identifier: 'ben@example.com' });
expect(outcome).toMatchObject({ kind: 'shared', email: 'ben@example.com' });
expect(handle.uploader.calls).toHaveLength(1);
expect(handle.uploader.calls[0]).toMatchObject({
identifier: 'ben@example.com',
owner: 'acme',
email: 'ben@example.com',
appVersion: '0.0.1',
timestamp: '2026-05-22T12:00:00Z',
});
Expand Down
19 changes: 10 additions & 9 deletions src/interactive/sharePrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface SharePromptInputs {
}

export type ShareOutcome =
| { kind: 'shared'; uploadId: string; identifier: string }
| { kind: 'shared'; uploadId: string; email: string }
| { kind: 'declined' }
| { kind: 'cancelled' }
| { kind: 'upload-failed'; message: string };
Expand Down Expand Up @@ -51,18 +51,19 @@ export async function runSharePrompt(inputs: SharePromptInputs): Promise<ShareOu
return { kind: 'declined' };
}

const identifierResult = await askForEmail(prompter);
if (identifierResult.kind === 'cancelled') {
const emailResult = await askForEmail(prompter);
if (emailResult.kind === 'cancelled') {
declinedOutro(inputs);
return { kind: 'cancelled' };
}
const identifier = identifierResult.identifier;
const email = emailResult.email;

const spinner = prompter.spinner();
spinner.start('Uploading...');
const uploadResult = await uploader.upload({
bytes: new TextEncoder().encode(inputs.htmlContent),
identifier,
owner: inputs.target,
email,
appVersion: inputs.context.appVersion,
timestamp: inputs.context.clock.now().toString(),
});
Expand All @@ -83,9 +84,9 @@ export async function runSharePrompt(inputs: SharePromptInputs): Promise<ShareOu
spinner.stop('Uploaded.');
analytics.capture('upload_succeeded', {});
prompter.outro(
`Thanks — you're on the PatchWave waitlist. We'll use this report to prioritize early access and follow up at ${identifier}.`,
`Thanks — you're on the PatchWave waitlist. We'll use this report to prioritize early access and follow up at ${email}.`,
);
return { kind: 'shared', uploadId, identifier };
return { kind: 'shared', uploadId, email };
}

function declinedOutro(inputs: SharePromptInputs): void {
Expand All @@ -102,7 +103,7 @@ function declinedOutro(inputs: SharePromptInputs): void {
prompter.outro('Done.');
}

async function askForEmail(prompter: Prompter): Promise<{ kind: 'ok'; identifier: string } | { kind: 'cancelled' }> {
async function askForEmail(prompter: Prompter): Promise<{ kind: 'ok'; email: string } | { kind: 'cancelled' }> {
const result = await prompter.text({
message: 'Email:',
placeholder: 'you@example.com',
Expand All @@ -120,5 +121,5 @@ async function askForEmail(prompter: Prompter): Promise<{ kind: 'ok'; identifier
}

const trimmed = result.value.trim();
return { kind: 'ok', identifier: trimmed };
return { kind: 'ok', email: trimmed };
}
7 changes: 3 additions & 4 deletions src/upload/Uploader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function presignResponse() {
}

describe('UploaderImpl', () => {
test("html kind: posts kind:'html' and PUTs raw bytes with text/html", async () => {
test('posts owner/email metadata and PUTs raw html bytes with text/html', async () => {
const { fetch, calls } = recordFetch([presignResponse(), new Response('', { status: 200 })]);
const bytes = htmlBytes.build();

Expand All @@ -39,13 +39,12 @@ describe('UploaderImpl', () => {
expect(calls[0]?.init?.method).toBe('POST');
const postBody = JSON.parse(calls[0]?.init?.body as string) as Record<string, unknown>;
expect(postBody).toMatchObject({
identifier: 'ben@example.com',
owner: 'acme',
email: 'ben@example.com',
appVersion: '0.0.1',
timestamp: '2026-05-22T12:00:00Z',
kind: 'html',
sizeBytes: bytes.byteLength,
});
expect(postBody).not.toHaveProperty('contentType');

expect(calls[1]?.url).toBe(presignResponseBody.build().presignedUrl);
expect(calls[1]?.init?.method).toBe('PUT');
Expand Down
7 changes: 4 additions & 3 deletions src/upload/Uploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export type UploadError =

export interface UploadInput {
readonly bytes: Uint8Array;
readonly identifier: string;
readonly owner: string;
readonly email: string;
readonly appVersion: string;
readonly timestamp: string;
}
Expand Down Expand Up @@ -55,10 +56,10 @@ export class UploaderImpl implements Uploader {

#requestPresign(input: UploadInput): ResultAsync<PresignResponse, UploadError> {
const body = JSON.stringify({
identifier: input.identifier,
owner: input.owner,
email: input.email,
appVersion: input.appVersion,
timestamp: input.timestamp,
kind: 'html',
sizeBytes: input.bytes.byteLength,
});
return ResultAsync.fromPromise(
Expand Down
3 changes: 2 additions & 1 deletion src/upload/testFactories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export const htmlBytes = Factory.define<Uint8Array>(() => new TextEncoder().enco

export const uploadInput = Factory.define<UploadInput>(() => ({
bytes: htmlBytes.build(),
identifier: 'ben@example.com',
owner: 'acme',
email: 'ben@example.com',
appVersion: '0.0.1',
timestamp: '2026-05-22T12:00:00Z',
}));
Expand Down