Build warnings
Every warning links to its section here. A line with more than one fault raises a warning for each, except inside one
defineEditableSource() call: a declaration reports its first fault, so fixing one can reveal the next.
These warnings are only shown for a deck on project file version 4. A deck below it sees none of them.
Warnings marked Shown with undiscoveredEditables print only with that flag on. They list everything the build
could not discover, so turn it on while upgrading a deck:
{
"warnings": { "undiscoveredEditables": true }
}
Markup
key-not-written-out
An editable key has to be written out as a string, so the build can add it to the built project. If you write it as an expression, the build cannot read the code, and the AI assistant won't be able to use the field.
Shown with undiscoveredEditables
fieldKey in data-editable={fieldKey}, so the AI assistant won't be able to use the field.- React
- Vanilla
export function Headline({ fieldKey }) {
return <p data-editable={fieldKey}>Our plans</p>;
}
<Headline fieldKey="title" />;
export function Headline() {
return <p data-editable="title">Our plans</p>;
}
<Headline />;
import { defineEditableSource, resolveEditable } from "@livepreso/webpack-deck";
export function Headline(props) {
const { attrs } = resolveEditable(editable, props);
return <p {...attrs}>Our plans</p>;
}
const editable = defineEditableSource(Headline, {
personal: { key: "{targetKey}-{id}-{instance}" },
});
<Headline id="title" />;
export function renderHeadline(options, element) {
element.innerHTML = `<p data-editable="title-${options.index}">Our plans</p>`;
}
renderHeadline({ index: 1 }, element);
export function renderHeadline(element) {
element.innerHTML = `<p data-editable="title-1">Our plans</p>`;
}
renderHeadline(element);
import { defineEditableSource, resolveEditable } from "@livepreso/webpack-deck";
export function renderHeadline(options, element) {
const { attrs } = resolveEditable(editable, options);
element.innerHTML = `<p ${toAttributeString(attrs)}>Our plans</p>`;
}
const editable = defineEditableSource(renderHeadline, {
personal: { key: "title-{id}" },
});
renderHeadline({ id: "1" }, element);
See:
toolbar-not-written-out
A data-toolbar has to be written out as a string of option names. If you write it as an expression, the build cannot
read the code. It will work for a user, but the AI assistant won't be able to use it.
Shown with undiscoveredEditables
toolbarOptions in data-toolbar={toolbarOptions}, so the AI assistant won't be able to edit the field. A user can still edit with the toolbar.- React
- Vanilla
export function Notes({ toolbarOptions }) {
return (
<div data-editable="notes" data-toolbar={toolbarOptions}>
Our notes
</div>
);
}
<Notes toolbarOptions="style color" />;
export function Notes() {
return (
<div data-editable="notes" data-toolbar="style color">
Our notes
</div>
);
}
<Notes />;
import { defineEditableSource, resolveEditable } from "@livepreso/webpack-deck";
export function Notes(props) {
const { attrs } = resolveEditable(editable, props);
return <div {...attrs}>Our notes</div>;
}
const editable = defineEditableSource(Notes, {
personal: { key: "{targetKey}-{id}-{instance}" },
});
<Notes id="notes" toolbar={["style", "color"]} />;
export function renderNotes(options, element) {
element.innerHTML = `<div data-editable="notes" data-toolbar="${options.toolbar}">Our notes</div>`;
}
renderNotes({ toolbar: "style color" }, element);
export function renderNotes(element) {
element.innerHTML = `<div data-editable="notes" data-toolbar="style color">Our notes</div>`;
}
renderNotes(element);
import { defineEditableSource, resolveEditable } from "@livepreso/webpack-deck";
export function renderNotes(options, element) {
const { attrs } = resolveEditable(editable, options);
element.innerHTML = `<div ${toAttributeString(attrs)}>Our notes</div>`;
}
const editable = defineEditableSource(renderNotes, {
personal: { key: "{targetKey}-{id}-{instance}" },
});
renderNotes({ id: "notes", toolbar: ["style", "color"] }, element);
See:
toolbar-option-not-recognised
Every option in a data-toolbar has to be an allowed option. Otherwise the AI assistant won't be able to edit the
field.
data-toolbar="style colour" includes colour.data-toolbar option can only be style, color, superscript, link, align, removeformat, list, table or format. The AI assistant won't be able to edit the field until every option is one of these.colour.- React
- Vanilla
<div data-editable="notes" data-toolbar="style colour">
Our notes
</div>
<div data-editable="notes" data-toolbar="style color">
Our notes
</div>
<div data-editable="notes" data-toolbar="style">
Our notes
</div>
<div data-editable="notes" data-toolbar="style colour">Our notes</div>
<div data-editable="notes" data-toolbar="style color">Our notes</div>
<div data-editable="notes" data-toolbar="style">Our notes</div>
See:
toolbar-option-needs-a-div
The list and table options only work on a <div>. On any other element the editor leaves those buttons out.
data-toolbar="style list" is on a <p>. The editor won't offer list on a <p>, only on a <div>.<p> to a <div>.list from data-toolbar.- React
- Vanilla
<p data-editable="notes" data-toolbar="style list">
Our notes
</p>
<div data-editable="notes" data-toolbar="style list">
Our notes
</div>
<p data-editable="notes" data-toolbar="style">
Our notes
</p>
<p data-editable="notes" data-toolbar="style list">Our notes</p>
<div data-editable="notes" data-toolbar="style list">Our notes</div>
<p data-editable="notes" data-toolbar="style">Our notes</p>
See:
attribute-on-a-component
An editable attribute written on a component only reaches the page if the component passes it on to an element. If it doesn't, the field won't appear on the page, but the AI assistant can still edit it. A component that spreads all of its props onto an element passes it on, and raises nothing.
Shown with undiscoveredEditables
data-editable="title" is on <Headline>.Headline is a component, so data-editable only reaches the page if Headline passes it on to an element. If it doesn't, the field won't appear on the page, but the AI assistant can still edit it.data-editable onto the element Headline renders.Headline pass its props on to that element:- React
export function Headline() {
return <p className="headline">Our plans</p>;
}
<Headline data-editable="title" />;
export function Headline() {
return (
<p className="headline" data-editable="title">
Our plans
</p>
);
}
<Headline />;
import { defineEditableSource, resolveEditable } from "@livepreso/webpack-deck";
export function Headline(props) {
const { attrs } = resolveEditable(editable, props);
return (
<p className="headline" {...attrs}>
Our plans
</p>
);
}
const editable = defineEditableSource(Headline, {
personal: { key: "{targetKey}-{id}-{instance}" },
});
<Headline id="title" />;
export function Headline(props) {
return (
<p className="headline" {...props}>
Our plans
</p>
);
}
<Headline data-editable="title" />;
See:
markers-built-into-an-object
You put an editable attribute in an object and spread the object onto an element. The build only finds attributes written directly on an element, so the AI assistant won't be able to edit the field.
Shown with undiscoveredEditables
{ "data-editable": "title" } sets data-editable on an object.data-editable where it's written on an element. The AI assistant won't be able to edit the field.data-editable on the element:- React
- Vanilla
export function PromoCard({ hasPersonalChannel }) {
const attributes = hasPersonalChannel ? { "data-editable": "card" } : { "data-companywide-editable": "card" };
return <div {...attributes}>Our offer</div>;
}
export function PromoCard() {
return <div data-editable="card">Our offer</div>;
}
import { defineEditableSource, prop, resolveEditable } from "@livepreso/webpack-deck";
export function PromoCard(props) {
const { attrs } = resolveEditable(editable, props);
return <div {...attrs}>Our offer</div>;
}
const editable = defineEditableSource(PromoCard, {
everyone: { key: "{targetKey}-{id}-{instance}" },
personal: { when: prop("hasPersonalChannel"), key: "{targetKey}-{id}-{instance}" },
});
<PromoCard id="card" hasPersonalChannel />;
export function renderPromoCard(options, element) {
const attributes = options.hasPersonalChannel ? { "data-editable": "card" } : { "data-companywide-editable": "card" };
element.innerHTML = `<div ${toAttributeString(attributes)}>Our offer</div>`;
}
export function renderPromoCard(element) {
element.innerHTML = `<div data-editable="card">Our offer</div>`;
}
import { defineEditableSource, prop, resolveEditable } from "@livepreso/webpack-deck";
export function renderPromoCard(options, element) {
const { attrs } = resolveEditable(editable, options);
element.innerHTML = `<div ${toAttributeString(attrs)}>Our offer</div>`;
}
const editable = defineEditableSource(renderPromoCard, {
everyone: { key: "{targetKey}-{id}-{instance}" },
personal: { when: prop("hasPersonalChannel"), key: "{targetKey}-{id}-{instance}" },
});
renderPromoCard({ id: "card", hasPersonalChannel: true }, element);
See:
description-without-a-field
You wrote a @public comment that isn't directly above a field. The comment only describes the element straight after
it, even when a field sits inside that element, so the AI assistant isn't told what the field is for.
@public comment describes only the element straight after it, so the AI assistant isn't told what the field is for.@public, if the comment doesn't describe a field.- Vanilla
<!-- @public The badge shown beside a plan's name. -->
<span class="badge-wrap">
<img data-companywide-dynamic-image="plan-badge" />
</span>
<span class="badge-wrap">
<!-- @public The badge shown beside a plan's name. -->
<img data-companywide-dynamic-image="plan-badge" />
</span>
<!-- The badge shown beside a plan's name. -->
<span class="badge-wrap">
<img data-companywide-dynamic-image="plan-badge" />
</span>
See:
description-written-twice
A description belongs to the key, not to whatever renders it, so exactly one line should carry it. Two that agree today are no better than two that differ: the next edit lands on one of them, and the one the build drops reads afterwards like a description nobody wrote.
- React
- Vanilla
defineEditableSource(Headline, {
everyone: { key: "{targetKey}-{id}-{instance}" },
});
<Headline id="title" description="The plan's headline." />
<Headline id="title" description="Repeats the plan name." />
defineEditableSource(Headline, {
everyone: { key: "{targetKey}-{id}-{instance}" },
});
<Headline id="title" description="The plan's headline." />
<Headline id="title" />
<!-- header.html -->
<!-- @public The plan's headline. -->
<h2 data-editable="title"></h2>
<!-- summary.html, on the same slide -->
<!-- @public Repeats the plan name. -->
<span data-editable="title"></span>
<!-- header.html -->
<!-- @public The plan's headline. -->
<h2 data-editable="title"></h2>
<!-- summary.html, on the same slide -->
<span data-editable="title"></span>
See:
Project file
keys-listed-twice
You list a key under editables: in project.yaml that the build also finds in your source. The build includes that
key either way, so the entry in project.yaml does nothing. This warning lists every such key, grouped by slide,
section or template.
title is listed under editables in project.yaml.title from project.yaml.slides:
- key: plans
editables:
- title
slides:
- key: plans
keys-only-in-project-file
You list a key under editables: in project.yaml that the build doesn't find in your source. Either nothing uses the
key any more, or your source builds it in a way the build can't read. This warning lists every such key, grouped by
slide, section or template.
Shown with undiscoveredEditables
title-old is listed under editables in project.yaml, but it isn't discovered in your source.title-old any more, or your source builds it as an expression the build can't read.title-old from project.yaml, if nothing uses it.title-old out in your source, or declare what renders it.slides:
- key: plans
editables:
- title-old
slides:
- key: plans
- React
- Vanilla
export function Headline({ fieldKey }) {
return <p data-editable={fieldKey}>Our plans</p>;
}
export function Headline() {
return <p data-editable="title-old">Our plans</p>;
}
element.innerHTML = `<p data-editable="${options.key}">Our plans</p>`;
element.innerHTML = `<p data-editable="title-old">Our plans</p>`;
Where the markup builds the key from content your deck authors, the keys are ones you know - they are the lines you
wrote in project.yaml. Put that content in a module the build reads, declare what renders the field, and the lines
come out of the project file.
A key built from a feed is the one shape with no answer. The rows arrive with the preso, so no list can name them, and
the field is not registered. Where that is what you want, say so on the line and take the key out of project.yaml:
<!-- livepreso-ignore-next-line -->
<div data-companywide-dynamic-image="<%= badge.code %>"></div>
See Silencing a warning.
See:
defineEditableSource()
Every warning here fails the build. None of the fields the declaration describes are registered.
declaration-not-written-out
The declaration has to be an object written at the call, because that is what the build reads the fields out of. A variable or a call only becomes an object when the deck runs, and the build reads your source without running it.
Headline's declaration has to be an object written at the defineEditableSource call. The build cannot read a variable or a function call in its place, so Headline's fields would be missing for the AI assistant, and the build fails.const into it is fine:const declaration = {
everyone: { key: "{targetKey}-{id}-{instance}" },
};
defineEditableSource(Headline, declaration);
A call in the same place is refused for the same reason:
defineEditableSource(Headline, buildDeclaration());
Inside the object, the ordinary rules for what the build can read apply, so spreading a
const into it is fine:
const shared = { everyone: { key: "{targetKey}-{id}-{instance}" } };
defineEditableSource(Headline, { ...shared, type: "richtext" });
defineEditableSource(Headline, {
everyone: { key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(Headline, {
everyone: { key: "{targetKey}-{id}-{instance}" },
personal: { when: prop("hasPersonalChannel"), key: "{targetKey}-{id}-{instance}" },
});
See:
declaration-has-no-name
You passed defineEditableSource something with no name, such as a class written in place. A declaration applies to
what renders the field by its name, so none of its fields reach the AI assistant, and the build fails.
class { ... } is passed to defineEditableSource as the thing that renders the field.defineEditableSource(class { ... }, {
everyone: { key: "{targetKey}-{id}-{instance}" },
});
export class Headline { ... }
defineEditableSource(Headline, {
everyone: { key: "{targetKey}-{id}-{instance}" },
});
See:
declaration-has-no-channel
Your declaration doesn't specify a channel. A field needs an everyone or personal channel to save into, so none of
its fields reach the AI assistant, and the build fails.
Headline's declaration doesn't specify a channel.everyone or personal channel to save what somebody types. Headline's fields would be missing for the AI assistant, so the build fails.everyone channel, shared by every preso made from the deck:personal channel, for one preso:defineEditableSource(Headline, {
type: "text",
});
defineEditableSource(Headline, {
type: "text",
everyone: { key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(Headline, {
type: "text",
personal: { key: "{targetKey}-{id}-{instance}" },
});
See:
channel-has-no-key
A channel in your declaration has no key. There's no default key, so its fields have nowhere to save, and the build
fails. This covers the everyone and personal channels alike.
Headline's personal channel has no key.Headline's fields would be missing for the AI assistant, so the build fails.personal channel a key.defineEditableSource(Headline, {
everyone: {},
});
defineEditableSource(Headline, {
everyone: { key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(Headline, {
personal: { when: prop("hasPersonalChannel") },
});
defineEditableSource(Headline, {
personal: { when: prop("hasPersonalChannel"), key: "{targetKey}-{id}-{instance}" },
});
See:
key-uses-an-unknown-placeholder
A key in your declaration uses a placeholder other than {targetKey}, {id} or {instance}. The build can't fill it
in, so those fields cannot be saved, and the build fails. This covers a key on the everyone or personal channel, and
a key in keyWhen.
Headline's everyone channel builds its key from {slide}, which the build cannot fill in.{targetKey}, {id} or {instance}. Headline's fields would be missing for the AI assistant, so the build fails.{targetKey}, {id} and {instance}.defineEditableSource(Headline, {
everyone: { key: "{slide}-{id}" },
});
defineEditableSource(Headline, {
everyone: { key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(Headline, {
personal: { key: "{slide}-{id}" },
});
defineEditableSource(Headline, {
personal: { key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(Headline, {
everyone: {
key: "{targetKey}-{id}-{instance}",
keyWhen: { shared: "{slide}" },
},
});
defineEditableSource(Headline, {
everyone: {
key: "{targetKey}-{id}-{instance}",
keyWhen: { shared: "{id}" },
},
});
See:
type-has-no-such-channel
You declared a video field with a personal channel. A video can only save to the everyone channel, so its fields
don't reach the AI assistant, and the build fails.
PromoVideo is a video, and its declaration has a personal channel.video field can only save to the everyone channel. PromoVideo's fields would be missing for the AI assistant, so the build fails.personal channel with an everyone channel.defineEditableSource(PromoVideo, {
type: "video",
personal: { key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(PromoVideo, {
type: "video",
everyone: { key: "{targetKey}-{id}-{instance}" },
});
See:
prop-has-two-defaults
Two prop() calls read the same argument with different defaults. An argument can only have one default, so the build
fails.
Headline's hasPersonalChannel argument has two different defaults.Headline's fields would be missing for the AI assistant, so the build fails.prop() only.defineEditableSource(Headline, {
props: {
prep: prop("hasPersonalChannel", { default: false }),
},
personal: {
when: prop("hasPersonalChannel", { default: true }),
key: "{targetKey}-{id}-{instance}",
},
});
defineEditableSource(Headline, {
props: {
prep: prop("hasPersonalChannel", { default: false }),
},
personal: {
when: prop("hasPersonalChannel"),
key: "{targetKey}-{id}-{instance}",
},
});
See:
field-option-outside-fields
You wrote id or description directly in a declaration, outside its fields object. Both must be declared inside a
fields object, and the build fails until they are.
Headline's declaration has id outside a fields object.id and description must be declared inside a fields object. Headline's fields would be missing for the AI assistant, so the build fails.id into a fields object.defineEditableSource(Headline, {
id: prop("idFromUsage"),
everyone: { key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(Headline, {
fields: { id: prop("idFromUsage") },
everyone: { key: "{targetKey}-{id}-{instance}" },
});
The same warning names description written outside fields:
defineEditableSource(Headline, {
description: prop("whatItIsFor"),
everyone: { key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(Headline, {
fields: { description: prop("whatItIsFor") },
everyone: { key: "{targetKey}-{id}-{instance}" },
});
See:
fields-is-empty
Your declaration has an empty fields list. The build fails until the list names the fields it renders, or the
declaration is removed.
SlideHeader's declaration has an empty fields list, so the build fails.YourEditable renders:defineEditableSource, if YourEditable renders nothing editable.function SlideHeader() { ... }
defineEditableSource(SlideHeader, {
fields: [],
everyone: { key: "{targetKey}-{id}-{instance}" },
});
function SlideHeader() { ... }
defineEditableSource(SlideHeader, {
fields: [{ id: "a-field" }],
everyone: { key: "{targetKey}-{id}-{instance}" },
});
function SlideHeader() { ... }
See:
field-needs-a-unique-id
An entry in your declaration's fields list is missing its id, has an id that is something other than a string or
prop(), or has the same id as another entry. The build fails until every entry has a unique id, and lists each
entry that needs one.
SlideHeader's fields list needs a unique id. An id can only be a string or prop(). The build will fail unless every entry listed below has one.id.function SlideHeader() { ... }
defineEditableSource(SlideHeader, {
fields: [
{ type: "image" },
{ id: 7 },
{ id: "a-third-field" },
{ id: "a-third-field", type: "image" },
],
everyone: { key: "{targetKey}-{id}-{instance}" },
});
function SlideHeader() { ... }
defineEditableSource(SlideHeader, {
fields: [
{ id: "a-field", type: "image" },
{ id: "another-field" },
{ id: "a-third-field" },
{ id: "a-fourth-field", type: "image" },
],
everyone: { key: "{targetKey}-{id}-{instance}" },
});
See:
fields-without-an-id-key
A fields list names several fields from one declaration, so every channel's key has to be built from {id} to tell
them apart. A key without {id} saves every entry under one key, so editing one changes them all.
SlideHeader's declaration has a fields list, so every key needs {id}. A key without {id} saves every field under one key, so editing one changes them all. The build will fail unless every key listed below has {id}.{id} to each key listed below.function SlideHeader() { ... }
defineEditableSource(SlideHeader, {
fields: [{ id: "a-field" }, { id: "another-field" }],
everyone: { key: "{targetKey}-header" },
personal: { key: "summary" },
});
function SlideHeader() { ... }
defineEditableSource(SlideHeader, {
fields: [{ id: "a-field" }, { id: "another-field" }],
everyone: { key: "{targetKey}-{id}-{instance}" },
personal: { key: "{targetKey}-{id}-{instance}" },
});
See:
declaration-rejected
A defineEditableSource call is missing something, or has something the build does not recognise. Each place below
names the declaration and what was wrong with it, and the warning for that fault says how to fix it.
defineEditableSource call listed below, for the reason beside it, so its fields would be missing for the AI assistant and the build fails.defineEditableSource(Headline, {
type: "richy",
everyone: { key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(Headline, {
type: "richtext",
everyone: { key: "{targetKey}-{id}-{instance}" },
});
See:
Usages
The examples in this section render these:
defineEditableSource(Headline, {
type: prop("kind", { default: "text" }),
everyone: { key: "{targetKey}-{id}-{instance}" },
personal: { when: prop("hasPersonalChannel"), key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(Disclaimer, {
everyone: { key: "{targetKey}-{id}-{instance}", scope: prop("scope", { default: "instance" }) },
});
defineEditableSource(PreparedFor, {
personal: { key: "{id}" },
});
defineEditableSource(Price, {
props: { currency: prop("currency") },
actions: price,
everyone: { key: "{targetKey}-{id}-{instance}" },
});
missing-id
A field is rendered with no id. Its key is built from the id, and the declaration gives the id no default, so there is no key to save it under, and the build fails.
Headline is rendered without an id. Its key is built from id, and its declaration doesn't give the id a default, so there's no key to save it under. The build will fail unless every place listed below has an id.id to each place listed below.- React
- Vanilla
<Headline hasPersonalChannel />
<Headline id="title" hasPersonalChannel />
Headline({ hasPersonalChannel: true });
Headline({ id: "title", hasPersonalChannel: true });
See:
unknown-field-type
A declaration reads its field type from an argument, and somewhere a field is rendered with a value that isn't text,
richtext, image or video. The type decides which data-* attribute the field renders, so the build fails until
every place uses one of those.
Headline's declaration (src/Headline.js:40) reads its field type from kind. The type picks which data-* attribute Headline renders, so kind can only be text, richtext, image or video. The build will fail unless every place listed below uses one of those.kind at each place listed below to text, richtext, image or video.- React
- Vanilla
<Headline id="title" kind="rich" />
<Headline id="title" kind="richtext" />
Headline({ id: "title", kind: "rich" });
Headline({ id: "title", kind: "richtext" });
See:
id-not-written-out
A field is rendered with its id written as an expression. The build reads your source without running the code, so the AI assistant won't be able to edit fields written that way.
Shown with undiscoveredEditables
id in <Headline id={plan.id} />, so the AI assistant won't be able to edit the fields listed below.id out as a string.fields.defineEditableSource returns the ids, call it in this file and pass a property of the result: const ids = makeIds(); ... id={ids[name]}.id it was given, declare that function with defineEditableSource.- React
- Vanilla
<Headline id={makeId("title")} />
<Headline id="title" />
defineEditableSource(Headline, {
everyone: { key: "..." },
});
plans.map((plan) => <Headline id={plan.id} />);
defineEditableSource(Headline, {
fields: [{ id: "basic" }, { id: "premium" }],
everyone: { key: "..." },
});
plans.map((plan) => <Headline id={plan.id} />);
function PlanTitle({ id }) {
return <Headline id={id} />;
}
function PlanTitle({ id }) {
return <Headline id={id} />;
}
defineEditableSource(PlanTitle, { everyone: { ... } });
function SectionBody({ ids, part }) {
return <Body id={ids[part]} />;
}
function SectionBody({ options, part }) {
const ids = sectionIds(options);
return <Body id={ids[part]} />;
}
Headline({ id: makeId("title") });
Headline({ id: "title" });
defineEditableSource(Headline, {
everyone: { key: "..." },
});
plans.map((plan) => Headline({ id: plan.id }));
defineEditableSource(Headline, {
fields: [{ id: "basic" }, { id: "premium" }],
everyone: { key: "..." },
});
plans.map((plan) => Headline({ id: plan.id }));
function planTitle({ id }) {
return Headline({ id });
}
function planTitle({ id }) {
return Headline({ id });
}
defineEditableSource(planTitle, { everyone: { ... } });
function sectionBody({ ids, part }) {
return Body({ id: ids[part] });
}
function sectionBody({ options, part }) {
const ids = sectionIds(options);
return Body({ id: ids[part] });
}
Where a slide renders one field per row, the ids usually come from content your deck authors. Put that content in a module the build can read and the ids are known:
export const PLANS = [{ id: "starter" }, { id: "growth" }, { id: "scale" }];
import { PLANS } from "./plans.js";
defineEditableSource(Headline, {
fields: PLANS,
everyone: { key: "{targetKey}-{id}" },
});
A key built from a feed is the one shape with no answer. The rows arrive with the preso, so no list can name them, and the field is not registered. Where that is what you want, say so on the line:
// livepreso-ignore-next-line id-not-written-out
<Badge id={badge.code} />
See Silencing a warning.
See:
- defineEditableSource() > fields
- defineEditableSource() > Ids from a declared function
- defineEditableSource() > prop()
unreadable-channel-argument
A channel can be switched on by an argument, with when: prop("..."). Where a field is rendered with a value the build
cannot read, it cannot tell whether that field has the channel. This covers the everyone and personal channels
alike.
Shown with undiscoveredEditables
hasPersonalChannel, so it treats every field listed below as having that channel. Where your deck leaves that channel unused, the AI assistant writes to a key the slide never shows. A user can still edit the field.hasPersonalChannel out where it is passed.hasPersonalChannel to a const in this file, or import one, where the value is the same on every rendering.prop() a default, where the value is decided while the slide runs.Headline switches its personal channel on from hasPersonalChannel, so the two keys it can store under depend on what
each rendering passes:
defineEditableSource(Headline, {
type: prop("kind", { default: "text" }),
everyone: { key: "{targetKey}-{id}-{instance}" },
personal: { when: prop("hasPersonalChannel"), key: "{targetKey}-{id}-{instance}" },
});
An everyone channel can be switched on the same way, and raises the same warning.
- React
- Vanilla
<Headline id="title" hasPersonalChannel={canOverride()} />
<Headline id="title" hasPersonalChannel />
const canOverride = true;
<Headline id="title" hasPersonalChannel={canOverride} />;
Headline({ id: "title", hasPersonalChannel: canOverride() });
Headline({ id: "title", hasPersonalChannel: true });
const canOverride = true;
Headline({ id: "title", hasPersonalChannel: canOverride });
A const works because the build follows it, in this file or one import away. A value decided while the slide runs
needs a default on the argument instead, which goes in the declaration:
defineEditableSource(Headline, {
type: prop("kind", { default: "text" }),
everyone: { key: "{targetKey}-{id}-{instance}" },
personal: {
when: prop("hasPersonalChannel", { default: true }),
key: "{targetKey}-{id}-{instance}",
},
});
The default says which channel the build registers. Where your deck does not use that channel, nothing is stored under that key and the AI assistant cannot edit the field. A user can still edit it.
See:
unreadable-key-choice
A keyWhen reads an argument to choose between two keys. Written as an expression, the build cannot read which of them
your deck stores under, so it registers neither.
Shown with undiscoveredEditables
Where project.yaml still lists one of the two keys, the field works and this warns. Where it lists neither, what a
user types is lost, so the build fails, and it is reported whether or not the flag is on.
scope, so it cannot tell which of the two keys each field listed below is saved under. The AI assistant cannot edit them, and a line marked not saved loses what a user types into it.Disclaimer takes its scope from an argument, so the two keys it can store under are pricing-fees-{instance} and
fees:
defineEditableSource(Disclaimer, {
everyone: { key: "{targetKey}-{id}-{instance}", scope: prop("scope", { default: "instance" }) },
});
- React
- Vanilla
<Disclaimer id="fees" scope={readScope()} />
<Disclaimer id="fees" scope="deck" />
Disclaimer({ id: "fees", scope: readScope() });
Disclaimer({ id: "fees", scope: "deck" });
See:
arguments-spread
A field is rendered with an argument spread the build cannot read, such as {...props}, so it cannot tell whether an
argument the declaration reads is inside it. A variable passed where a plain call takes its arguments, as in
headline(options), is read the same way. For a function that returns ids, this shows only where no call on the slide
can be read - see Ids from a declared function.
Shown with undiscoveredEditables
{...props}, so it cannot tell whether it carries the arguments Headline reads: id, kind and hasPersonalChannel. The AI assistant won't be able to edit the fields listed below.id, kind and hasPersonalChannel as separate arguments.defineEditableSource.- React
- Vanilla
function PlanCard(props) {
return <Headline {...props} />;
}
function PlanCard(props) {
return <Headline {...props} id="title" kind="text" hasPersonalChannel />;
}
The rendering stays as it is under this one. Declaring the function tells the build what the spread carries:
function PlanCard(props) {
return <Headline {...props} />;
}
defineEditableSource(PlanCard, { everyone: { ... } });
function planCard(options) {
return Headline({ ...options });
}
function planCard(options) {
return Headline({ ...options, id: "title", kind: "text", hasPersonalChannel: true });
}
The rendering stays as it is under this one. Declaring the function tells the build what the spread carries:
function planCard(options) {
return Headline({ ...options });
}
defineEditableSource(planCard, { everyone: { ... } });
See:
description-not-written-out
A field's description is written as an expression, so the build can't read what it says. The field still works; only the description is lost.
<Headline id="title" description={titleText} />, so the AI assistant won't know what the fields listed below are for. The fields still work.- React
- Vanilla
<Headline
id="title"
description={describe("title")}
/>
<Headline
id="title"
description="The headline at the top of the slide."
/>
Headline({
id: "title",
description: describe("title"),
});
Headline({
id: "title",
description: "The headline at the top of the slide.",
});
See:
toolbar-argument-not-written-out
A declaration reads its toolbar from an argument, and a field is rendered with that toolbar written as an expression. The build cannot read it, so the AI assistant won't be able to edit the field. It still works for a user.
<Headline id="title" toolbar={pickToolbar("simple")} />, so the AI assistant won't be able to edit the fields listed below. The fields still work for a user.TOOLBARS.simple.- React
- Vanilla
<Headline
id="title"
toolbar={pickToolbar("simple")}
/>
<Headline
id="title"
toolbar={["bold", "italic"]}
/>
const TOOLBARS = {
simple: ["bold", "italic"],
full: ["style", "color", "list"],
};
<Headline id="title" toolbar={TOOLBARS.simple} />;
Headline({
id: "title",
toolbar: pickToolbar("simple"),
});
Headline({
id: "title",
toolbar: ["bold", "italic"],
});
const TOOLBARS = {
simple: ["bold", "italic"],
full: ["style", "color", "list"],
};
Headline({ id: "title", toolbar: TOOLBARS.simple });
See:
copy-not-written-out
The text between a field's tags is what it shows before anybody edits it. The build cannot read children written as an expression, so the AI assistant won't know what the field starts as. The field still works for a user.
Shown with undiscoveredEditables
{plan.name}, so the AI assistant won't know what the fields listed below show before anyone edits them. The fields still work for a user.<b>Our plans</b>.The build reads a const:
const PLAN = { name: "Growth" };
<Headline id="title">{PLAN.name}</Headline> // read
<Headline id="title">{readPlanName()}</Headline> // not read
- React
<Headline id="title" hasPersonalChannel>
{plan.name}
</Headline>
<Headline id="title" hasPersonalChannel>
Our plans
</Headline>
Children built from a feed are the one shape with no answer. The rows arrive with the preso, so nothing in your source says what the field starts as. Where that is what you want, say so on the line:
{
/* livepreso-ignore-next-line copy-not-written-out */
}
<Headline id="title">{plan.name}</Headline>;
The AI assistant still won't know what the field starts as - see Silencing a warning.
See:
default-not-written-out
A declaration can take a field's starting copy from one of its arguments, so the copy lives at the place the field is rendered instead of between its tags. The build reads that argument the same way it reads children, and an expression it cannot read leaves the AI assistant with nothing to say the field starts as.
Header's declaration takes its default copy from title, and the build cannot read what each place listed below passes. The AI assistant won't know what those fields show before anyone edits them. The fields still work for a user.Header is rendered.const or a .json key, which the build reads.title a default in Header's declaration.The build follows a const and a .json key, through as many files as the value passes:
import copy from "./copy.json";
<Header id="intro" title={copy.heading} /> // read
<Header id="intro" title={buildHeading()} /> // not read
- React
{
}
<Header id="intro" title={copy.heading[locale]} />;
{
}
<Header id="intro" title={copy.heading} />;
See:
actions-prop-not-written-out
A declaration's actions read one of its arguments as a prop, and a field is rendered with that argument written as an expression. The build cannot read it, so the AI assistant won't know the value. The field still works for a user.
currency={readCurrency()}, so the AI assistant won't know the currency that Price's actions read. The fields listed below still work for a user.- React
- Vanilla
<Price id="monthly" currency={readCurrency()} />
<Price id="monthly" currency="AUD" />
Price({ id: "monthly", currency: readCurrency() });
Price({ id: "monthly", currency: "AUD" });
See:
actions-prop-missing
A declaration's actions read one of its arguments as a prop, the declaration gives it no default, and a field is rendered without it. The AI assistant won't know the value. The field still works for a user.
Price's actions read currency, and Price's declaration doesn't give currency a default. The AI assistant won't know the value of currency for the fields listed below. The fields still work for a user.currency at each place listed below.currency a default in Price's declaration.- React
- Vanilla
<Price id="monthly" />
<Price id="monthly" currency="AUD" />
defineEditableSource(Price, {
props: { currency: prop("currency", { default: "AUD" }) },
actions: price,
everyone: { ... },
});
Price({ id: "monthly" });
Price({ id: "monthly", currency: "AUD" });
defineEditableSource(Price, {
props: { currency: prop("currency", { default: "AUD" }) },
actions: price,
everyone: { ... },
});
See:
defineActions()
actions-value-not-readable
A declaration's actions has to be written out beside the component, so the build can copy it into the module the AI
assistant calls. Given a function call or a variable, there is nothing written out to copy.
actions has to be given your queries and mutations, or a const you assigned them to. The build will fail until each declaration listed below gives one of those.actions your queries and mutations:actions a const you assigned them to, here or imported:defineEditableSource(Price, {
actions: buildActions(),
everyone: { key: "{targetKey}-{id}-{instance}" },
});
defineEditableSource(Price, {
actions: {
queries: { get: ({ state }) => state },
mutations: { set: ({ state, input }) => input },
},
everyone: { key: "{targetKey}-{id}-{instance}" },
});
See:
actions-not-written-out
defineActions reads its queries and mutations from the object written out inside the call. Given a variable, a
function call or nothing, its queries and mutations would be missing for the AI assistant.
defineActions reads queries and mutations from an object written out inside it. Each defineActions listed below is given a variable, a function call or nothing, so its queries and mutations would be missing for the AI assistant. The build will fail until each one has its object written out.defineActions listed below.export const price = defineActions(
buildPriceActions(),
);
export const price = defineActions({
defaultState: { amount: 0 },
queries: {
formatted: ({ state }) => state.amount,
},
});
See:
actions-use-an-unavailable-name
Actions written in the same file as what renders the field can use that file's imports, top-level consts and
functions - everything except what renders the field itself. This warning names a top-level name the actions use that
isn't one of those: a let or var, what renders the field, or a name the build can't follow.
const, or as a function.defineActions in a separate file, and import them.let TAX = 0.1;
defineEditableSource(Price, {
actions: {
queries: {
total: ({ state }) => state.amount * (1 + TAX),
},
},
everyone: { ... },
});
const TAX = 0.1;
defineEditableSource(Price, {
actions: {
queries: {
total: ({ state }) => state.amount * (1 + TAX),
},
},
everyone: { ... },
});
// price-actions.js
let TAX = 0.1;
export const price = defineActions({
queries: { total: ({ state }) => state.amount * (1 + TAX) },
});
// price.js
import { price } from "./price-actions.js";
defineEditableSource(Price, { actions: price, everyone: { ... } });
The same warning names what renders the field, where the actions read something off it. The actions are copied into a module the AI assistant calls with no slide open, and that module cannot reach back into the file it came from:
function Price(props) { ... }
Price.currency = "USD";
defineEditableSource(Price, {
actions: {
queries: {
total: ({ state }) => `${Price.currency}${state.amount}`,
},
},
everyone: { ... },
});
const CURRENCY = "USD";
function Price(props) { ... }
defineEditableSource(Price, {
actions: {
queries: {
total: ({ state }) => `${CURRENCY}${state.amount}`,
},
},
everyone: { ... },
});
Where the actions were reaching the component for something on the page, name rendered instead - see
reaches-the-page.
See:
arguments-not-destructured
The build uses the names destructured in a query or mutation's parameter to decide where the AI assistant can use it. A
parameter taken as one name instead, such as (args), can only be written destructured.
({ state, input }), to decide where the AI assistant can use it. The parameter can only be written that way. The build will fail until each one listed below destructures its parameter.queries: {
formatted: (args) => args.state.amount,
},
queries: {
formatted: ({ state }) => state.amount,
},
See:
argument-not-used
The arguments a query or mutation names decide where it can be used. Naming cmsvals or feeds says it needs that
value, so it is only offered where that value is supplied.
queries: {
formatted: ({ state, feeds }) => state.amount,
},
queries: {
formatted: ({ state }) => state.amount,
},
See:
reaches-the-page
document, window and Bridge only exist while a slide is open, and the AI assistant can call a query or mutation
with no slide open. A query or mutation that uses one of them directly can only run on the slide.
document, window and Bridge only exist while a slide is open, and the AI assistant can call a query or mutation with no slide open. Each query or mutation listed below uses one of them. The build will fail until each one reads what it needs through rendered.rendered, which the deck provides with provideRendered. For Bridge, provide a function that calls it.- React
- Vanilla
export const price = defineActions({
queries: {
width: () => document.querySelector(".price").clientWidth,
plans: () => Bridge.Feeds.read("plans"),
},
});
function Price(props) {
const { queries } = useActions(price, props);
return <span className="price">...</span>;
}
export const price = defineActions({
queries: {
width: ({ rendered }) => rendered.label.clientWidth,
plans: ({ rendered }) => rendered.loadPlans(),
},
});
function Price(props) {
const label = useRef(null);
const loadPlans = useRef(() => Bridge.Feeds.read("plans"));
const { queries } = useActions(price, props, { rendered: { label, loadPlans } });
return <span ref={label}>...</span>;
}
export const price = defineActions({
queries: {
width: () => document.querySelector(".price").clientWidth,
plans: () => Bridge.Feeds.read("plans"),
},
});
function renderPrice(element, props) {
const field = bindActions(price, props);
element.innerHTML = `<span class="price">...`;
}
function renderPrice(element, props) {
const field = bindActions(price, props);
element.innerHTML = `<span>...</span>`;
// Call release() when the field leaves the screen.
const release = provideRendered(field.key, {
label: element.firstElementChild,
loadPlans: () => Bridge.Feeds.read("plans"),
});
return release;
}
The slide hands over the element and a function that calls Bridge: in React through useActions()'s rendered
option, which takes refs; in vanilla through provideRendered(). A query then reads both from rendered, and a call
waits until the slide has provided them.
See:
doc-comment-above-run
In a query or mutation written as an object, with run inside it, the build reads the doc comment directly above run.
A comment above the object is not read, so the AI assistant is told nothing about what it is for, and a mutation refuses
every call that sends input.
run. The build will fail until each comment listed below is there.run.mutations: {
/**
* Change the price.
*
* @param {object} args
* @param {object} args.input
* @param {number} args.input.amount the new price
*/
set: {
channels: ["everyone"],
run: ({ state, input }) => {
state.amount = input.amount;
},
},
},
mutations: {
set: {
channels: ["everyone"],
/**
* Change the price.
*
* @param {object} args
* @param {object} args.input
* @param {number} args.input.amount the new price
*/
run: ({ state, input }) => {
state.amount = input.amount;
},
},
},
See:
input-not-json
The AI assistant sends a query or mutation's input as JSON, which carries a string, number, boolean, array, plain
object or null. There is no way to send a key typed as anything else, such as a Date or a function.
mutations.set takes input.when as a Date.Date, so the build fails./**
* @param {object} args
* @param {object} args.input
* @param {Date} args.input.when
*/
/**
* @param {object} args
* @param {object} args.input
* @param {string} args.input.when the date, as YYYY-MM-DD
*/
See:
input-not-described
A mutation's doc comment describes each value inside input, under the object the mutation receives. Two shapes miss
it:
inputdescribed as the whole object. The mutation refuses every call that sends input.inputnamed on its own. The mutation takes any object the AI assistant sends.
({ state, input }) as input, so the build fails.input inside it.mutations: {
/**
* @param {object} input
*/
set: ({ state, input }) => {
state.amount = input.amount;
},
/**
* @param {object} args
* @param {object} args.input
*/
save: ({ state, input }) => { ... },
},
mutations: {
/**
* @param {object} args
* @param {object} args.input
* @param {number} args.input.amount the new price
*/
set: ({ state, input }) => {
state.amount = input.amount;
},
/**
* @param {object} args
* @param {object} args.input
* @param {number} args.input.amount the new price
*/
save: ({ state, input }) => { ... },
},
set: ({ input }: { input: { amount: number } }) => { ... },
See:
Silencing a warning
livepreso-ignore-next-line
Silences the warnings on the line below. Use it only where a value really is decided at runtime.
It silences the warning, not the problem. Everything the warning said the AI assistant would lose is still lost - a key it cannot read stays unregistered, and copy it cannot read stays unknown. What the comment buys is a build that stops telling you about a line you have already decided about.
// Every warning on the next line.
// livepreso-ignore-next-line
<Price id={plan.id} currency="AUD" />
// Only the warnings named.
// livepreso-ignore-next-line id-not-written-out, arguments-spread
<Price id={plan.id} {...plan} />
<!-- livepreso-ignore-next-line key-not-written-out -->
ignore-comment-does-nothing
A livepreso-ignore-next-line is above code that doesn't raise a warning.
livepreso-ignore-next-line listed below is above code that doesn't raise a warning.- React
- Vanilla
{
}
{
/* livepreso-ignore-next-line */
}
<Headline id="title" hasPersonalChannel />;
<Headline id="title" hasPersonalChannel />
<!-- livepreso-ignore-next-line -->
<div data-editable="notes">Our notes</div>
<div data-editable="notes">Our notes</div>
See:
ignore-comment-names-an-unknown-warning
A livepreso-ignore-next-line names a warning that doesn't exist.
livepreso-ignore-next-line listed below names a warning that doesn't exist.- React
- Vanilla
{
}
{
/* livepreso-ignore-next-line id-not-writen-out */
}
<Price id={plan.id} currency="AUD" />;
{
}
{
/* livepreso-ignore-next-line id-not-written-out */
}
<Price id={plan.id} currency="AUD" />;
{
}
{
/* livepreso-ignore-next-line */
}
<Price id={plan.id} currency="AUD" />;
<!-- livepreso-ignore-next-line key-not-writen-out -->
<div data-editable="${titleKey}">Our plans</div>
<!-- livepreso-ignore-next-line key-not-written-out -->
<div data-editable="${titleKey}">Our plans</div>
<!-- livepreso-ignore-next-line -->
<div data-editable="${titleKey}">Our plans</div>
See:
showSilenced
Lists every silenced warning on each build.
{
"warnings": { "showSilenced": true }
}
editable discovery (silenced)
2 warnings are silenced by a livepreso-ignore-next-line comment.
src/welcome.js:12:4 id-not-written-out
src/closing.js:30:8 arguments-spread