Skip to content
Open
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
14 changes: 13 additions & 1 deletion packages/host/app/components/host-mode/card.gts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface Signature {
Args: {
cardId: string | null;
displayBoundaries?: boolean;
isPrimary?: boolean;
openInteractSubmode?: () => void;
};
}
Expand Down Expand Up @@ -52,7 +53,7 @@ export default class HostModeCard extends Component<Signature> {

<template>
<CardContainer
class='host-mode-card'
class='host-mode-card {{if @isPrimary "is-primary"}}'
displayBoundaries={{@displayBoundaries}}
...attributes
>
Expand Down Expand Up @@ -118,6 +119,17 @@ export default class HostModeCard extends Component<Signature> {
text-align: center;
gap: var(--boxel-sp);
}

@media print {
.host-mode-card.is-primary {
display: contents;
}

.host-mode-card.is-primary .card {
max-height: none;
overflow: visible;
}
}
</style>
</template>
}
3 changes: 2 additions & 1 deletion packages/host/app/components/host-mode/content.gts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export default class HostModeContent extends Component<Signature> {
<HostModeCard
@cardId={{@primaryCardId}}
@displayBoundaries={{not this.isWideCard}}
@isPrimary={{true}}
@openInteractSubmode={{@openInteractSubmode}}
class='current-card'
/>
Expand All @@ -125,7 +126,7 @@ export default class HostModeContent extends Component<Signature> {
align-items: center;
justify-content: center;
width: 100%;
max-height: 100vh;
min-height: 100vh;
overflow: hidden;
Comment on lines +129 to 130
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from max-height to min-height combined with overflow: hidden may clip printed content that exceeds the viewport height. While print mode typically handles overflow differently than screen display, it's best practice to explicitly set overflow: visible in a print media query for the .host-mode-content element to ensure multi-page content is not inadvertently clipped across different browsers.

Copilot uses AI. Check for mistakes.
padding: var(--boxel-sp);
position: relative;
Expand Down
10 changes: 0 additions & 10 deletions packages/host/app/templates/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -232,23 +232,13 @@ export class IndexComponent extends Component<IndexComponentComponentSignature>
@stackItemCardIds={{this.hostModeStateService.stackItems}}
@removeCardFromStack={{this.removeCardFromStack}}
@viewCard={{this.viewCard}}
class='host-mode-content'
{{this.removeIsolatedMarkup}}
/>
{{/if}}
{{else}}
{{pageTitle this.operatorModeStateService.title}}
<OperatorModeContainer @onClose={{this.closeOperatorMode}} />
{{/if}}

<style scoped>
.host-mode-content {
height: 100%;
position: fixed;
top: 0;
left: 0;
}
</style>
</template>
}

Expand Down
90 changes: 87 additions & 3 deletions packages/matrix/tests/host-mode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { randomUUID } from 'crypto';
test.describe('Host mode', () => {
let publishedRealmURL: string;
let publishedCardURL: string;
let publishedWhitePaperCardURL: string;
let connectRouteURL: string;
let username: string;
let password: string;
Expand Down Expand Up @@ -51,6 +52,54 @@ test.describe('Host mode', () => {
`,
);

await postCardSource(
page,
realmURL,
'white-paper-card.gts',
`
import { CardDef, Component } from 'https://cardstack.com/base/card-api';

export class WhitePaperCard extends CardDef {
static prefersWideFormat = true;

static isolated = class Isolated extends Component<typeof this> {
<template>
<article class='white-paper' data-test-white-paper>
<section class='page-block'>Page 1</section>
<section class='page-block'>Page 2</section>
<section class='page-block'>Page 3</section>
</article>
<style scoped>
.white-paper {
padding: 0;
margin: 0;
font-family: serif;
}

.page-block {
height: 9.5in;
padding: 0.75in;
box-sizing: border-box;
}

@media print {
.page-block {
break-after: page;
page-break-after: always;
}

.page-block:last-child {
break-after: auto;
page-break-after: auto;
Comment on lines +88 to +93
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test uses both modern (break-after) and legacy (page-break-after) CSS properties for page breaks. While this provides backward compatibility, the legacy page-break-after property is deprecated in favor of break-after. Since this is test code and the modern property is already present, consider documenting why both are needed or removing the legacy property if browser compatibility is not a concern for the test environment.

Suggested change
page-break-after: always;
}
.page-block:last-child {
break-after: auto;
page-break-after: auto;
}
.page-block:last-child {
break-after: auto;

Copilot uses AI. Check for mistakes.
}
}
</style>
</template>
};
}
`,
);

await postCardSource(
page,
realmURL,
Expand All @@ -69,6 +118,24 @@ test.describe('Host mode', () => {
}),
);

await postCardSource(
page,
realmURL,
'white-paper.json',
JSON.stringify({
data: {
type: 'card',
attributes: {},
meta: {
adoptsFrom: {
module: './white-paper-card.gts',
name: 'WhitePaperCard',
},
},
},
}),
);

await page.reload();
await page.locator('[data-test-host-mode-isolated]').waitFor();

Expand Down Expand Up @@ -107,6 +174,7 @@ test.describe('Host mode', () => {
);

publishedCardURL = `${publishedRealmURL}index.json`;
publishedWhitePaperCardURL = `${publishedRealmURL}white-paper.json`;
connectRouteURL = `http://localhost:4205/connect/${encodeURIComponent(
publishedRealmURL,
)}`;
Expand All @@ -133,9 +201,25 @@ test.describe('Host mode', () => {
expect(html).toContain('data-test-host-mode-isolated');

await page.goto(publishedCardURL);
await expect(
page.locator('[data-test-host-mode-isolated]'),
).toBeVisible();
await expect(page.locator('[data-test-host-mode-isolated]')).toBeVisible();
});

test('printed isolated card produces a stable page count', async ({
page,
}) => {
await page.goto(publishedWhitePaperCardURL);
await page.locator('[data-test-white-paper]').waitFor();
await waitUntil(
async () =>
(await page.locator('[data-test-host-loading]').count()) === 0,
);

await page.emulateMedia({ media: 'print' });
let pdf = await page.pdf({ format: 'Letter', printBackground: true });
let pageCount =
pdf.toString('latin1').match(/\/Type\s*\/Page\b/g)?.length ?? 0;

expect(pageCount).toBe(3);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I didn’t know Playwright could do this, very useful!

});

test.skip('card in a published realm renders in host mode with a connect button', async ({
Expand Down
Loading