Skip to content

feat: show a live preview beside the document - #273

Closed
PhilReinking wants to merge 22 commits into
feat/bits-282c-keyboard-movefrom
feat/bits-285-preview-panel
Closed

PhilReinking wants to merge 22 commits into
feat/bits-282c-keyboard-movefrom
feat/bits-285-preview-panel

Conversation

@PhilReinking

@PhilReinking PhilReinking commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

"Preview" in the editor shows the form in preview mode: beside the document on a desktop, as a panel on tablet and phone. It follows the open question, and Next in the preview opens that question in the document.

Fixes BITS-285

  • Each save that changes the form reloads the preview at the open question, also when two saves overlap. Opening a card keeps it.
  • The preview starts at the open question, or at once when none is open.
  • Heading "Preview · question 3"; text instead of a blank frame for an empty form.
  • A grey line says why the open question can't show: hidden, in a hidden group, or by a rule.
  • Phone: Outline and Preview are 44 px icons below sm. The form menu's Preview opens the panel; other pages open ?preview=1 in a new tab.
  • On close, focus goes back to Preview or the form menu. A window narrower than 1280 px closes the preview.
  • Messages both ways check origin and source.

Tested: Vitest, lint, vue-tsc, build (stacked, no CI); by hand at 1440, 1024, 390 on a draft and a live form, nothing stored. New tests fail without the change.

Not tested: Safari and Firefox.

Screenshots: on BITS-285.

🤖 Generated with Claude Code

PhilReinking and others added 13 commits October 13, 2026 02:17
Preview in the top bar shows the public form in preview mode: beside
the document on a desktop, as a panel over it on tablet and phone.
It follows the open question, and the document follows Next and Back
in the preview. A saved change reloads it at the open question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The preview shows a small note instead of the owner's orange bar, also
on a copied preview link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The phone's top bar keeps its room for the form name, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The phone form menu and the bar's default Preview link opened the real
form, so answers given there were stored. Both now add ?preview=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mouse click on the button's text landed on an inner span. On close the
dialog tried to focus that span, which can't take focus, so focus fell to
the page. The button's inside now lets clicks through to the button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The button is 32 px high. An invisible edge 6 px above and below makes
the tap area 44 px without changing its look.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shrinking the window below 1280 px, or turning a tablet to portrait, opened
the preview as a panel over the document and moved focus into it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A form without questions showed a blank white frame, which looked broken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… open one

The heading reads "Preview · question 3". When the open question is hidden
by a rule, the preview stays where it is; a grey line now says why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to feat/bits-285-preview-panel

Conflict in the document's count row: part b's Outline button and the
preview's actions slot now sit side by side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With both buttons the row needs about 376 px but has 296 px at 360. Below
sm both are now 44 px icon buttons, their labels come back from sm, and
Preview looks like Outline beside it. Clicks land on the buttons, so focus
can go back to them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the editor, the form menu's Preview now opens the preview panel at the
open question, as the Preview button does. That button scrolls away on a
long form; the menu stays in reach. Other pages keep the new tab in preview
mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@PhilReinking PhilReinking left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changes at cbfd30d. No CI (stacked), local runs: Vitest 314, lint, vue-tsc, build, PHP suite on MariaDB 743 passed (one known flaky storyboard test failed).

One must, two shoulds inline. Held: the new tests fail on the base, messages pass only between the editor and its own frame, a full preview run (draft and live, with a file) stores nothing, steady typing reloads once, and Esc and Close return focus to Preview.

Comment on lines +71 to +74
// every save that works clears its error; rules save on their own
useNotice().$onAction(({ name, after }) => {
if (name === "clearSaveError") after(reload);
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Deleting a question, a choice or a rule doesn't reload the preview. The stores call clearSaveError before they remove the item, so reload still sees the old list and stops. In a live run the preview kept a deleted choice until the next edit. Checking one tick later fixes it, and the reload test then needs one more await nextTick():

Suggested change
// every save that works clears its error; rules save on their own
useNotice().$onAction(({ name, after }) => {
if (name === "clearSaveError") after(reload);
});
// every save that works clears its error, then the store takes in the answer
// (a deleted question leaves the list only after that); rules save on their own
useNotice().$onAction(({ name, after }) => {
if (name === "clearSaveError") after(() => nextTick(reload));
});

return;
}

loaded = content();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When two saves overlap, the preview misses the second one. Example: edit the question, then a choice within a second. The question's save reloads the preview and counts the unsaved choice as shown, so the choice's save reloads nothing. In a live run the server had the new choice "Green" and the preview kept "Red" until the next edit. Maybe count an edit as shown only once its own save is back.

Comment on lines +41 to +46
import { useMediaQuery } from "@vueuse/core";
import PreviewFrame from "./PreviewFrame.vue";

const open = defineModel<boolean>({ required: true });

const isWide = useMediaQuery("(min-width: 1280px)");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

On a desktop the preview is on, so a window that gets narrower than 1280 px opens the panel over the document and moves the focus into it. I would close it when the window gets narrower:

Suggested change
import { useMediaQuery } from "@vueuse/core";
import PreviewFrame from "./PreviewFrame.vue";
const open = defineModel<boolean>({ required: true });
const isWide = useMediaQuery("(min-width: 1280px)");
import { useMediaQuery } from "@vueuse/core";
import { watch } from "vue";
import PreviewFrame from "./PreviewFrame.vue";
const open = defineModel<boolean>({ required: true });
const isWide = useMediaQuery("(min-width: 1280px)");
// a window that gets narrower keeps the document: the panel opens only on a click
watch(isWide, (wide) => {
if (!wide) open.value = false;
});

The stores clear the save error before they take the item out, so the
reload still saw the old list and kept the preview. It now waits a tick.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PhilReinking
PhilReinking changed the base branch from feat/bits-282-document-editor to feat/bits-282b-outline-phone October 13, 2026 00:52
PhilReinking and others added 5 commits October 13, 2026 03:03
A save that came back first reloaded the preview and counted a change that
was still waiting to save as shown, so that change's own save reloaded
nothing. Now each save that carries a change reloads once it is back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The questions load after the panel, so the first preview had no question
to start at, began at question 1 and then jumped. It now waits for the
open question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel opened while the menu was still closing, so it remembered the
gone menu item and the name inside the menu button, and closing it left
focus on the page. It now opens once the menu gave focus back to its
button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to feat/bits-285-preview-panel

# Conflicts:
#	resources/js/Pages/Forms/Edit.vue
…to feat/bits-285-preview-panel

# Conflicts:
#	resources/js/components/Factory/Document/FormDocument.test.ts
@PhilReinking
PhilReinking changed the base branch from feat/bits-282b-outline-phone to feat/bits-282c-keyboard-move October 13, 2026 01:16

@PhilReinking PhilReinking left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK at 12ed786 (re-check of cbfd30d..12ed786). No CI (stacked), local runs: Vitest 386, lint, vue-tsc, build.

The must and both shoulds are fixed: a deleted question, choice or rule reloads the preview, two overlapping saves both show, and a narrower window closes the panel. The 15 new tests fail on the old code. Two new shoulds inline. Held: the merge keeps parts b and c, and the message checks are unchanged.

Comment on lines +39 to +48
// the questions load after the panel: start at the open one, not at the
// first and then jump
const src = ref<string>();
watch(
() => workbench.block,
(block) => {
if (block) src.value ??= url();
},
{ immediate: true },
);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should: a panel that opens while no question is open stays blank, with only "Preview" in it. On a phone: delete the open question, then tap Preview. On a desktop: Escape in a question, then Preview off and on. Starting at once when the questions are already loaded fixes it. The "waits for the questions" test then mounts with blocks = null (I ran it: 18 of 18 pass).

Suggested change
// the questions load after the panel: start at the open one, not at the
// first and then jump
const src = ref<string>();
watch(
() => workbench.block,
(block) => {
if (block) src.value ??= url();
},
{ immediate: true },
);
// the questions load after the panel: start at the open one, not at the
// first and then jump; a panel opened later with none open starts at once
const src = ref(store.blocks ? url() : undefined);
watch(
() => workbench.block,
(block) => {
if (block) src.value ??= url();
},
);

Comment on lines +11 to +14
<p v-if="hiddenNumber" class="w-80 max-w-full text-xs text-grey-600">
Question {{ hiddenNumber }} is hidden by a rule, so the preview can't jump
to it.
</p>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should: the server leaves a Hidden question (and every question in a Hidden group) out of the public form. Opening one says "hidden by a rule", so the owner looks for a rule that isn't there. Maybe "Question {{ hiddenNumber }} is hidden or skipped by a rule, so the preview can't show it." The panel below has the same text. Not run.

PhilReinking and others added 3 commits October 13, 2026 03:24
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hidden question, or one in a hidden group, is left out of the public
form; the note blamed a rule for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@PhilReinking PhilReinking left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK at a1f2a83. No CI (stacked), local runs: Vitest 389, lint, vue-tsc, build.

Both shoulds are fixed, and the 3 new tests fail on 12ed786. On a phone, deleting the open question and then tapping Preview runs the form at question 1. The note names the right reason for a hidden question, a question in a hidden group and one hidden by a rule. A full preview run stored nothing.

@PhilReinking

Copy link
Copy Markdown
Contributor Author

Landed in main with #282 (the editor rebuild as one PR); this PR's commits are part of it.

ferbinu pushed a commit to ferbinu/input that referenced this pull request Oct 15, 2026
deck9#273

deck9#274 saves each rule with saveSingleRule; saveBlockLogic is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ferbinu pushed a commit to ferbinu/input that referenced this pull request Oct 15, 2026
…eck9#276 and deck9#273

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant