Repository: Thinkmill/keystatic
Pull request #150 (2023-03-09 00:23:26)

Little fixes
- Adds `description` to all the other fields
- Skip attempting to get a GitHub auth token when using local mode (note this never caused a redirect or anything previously, it just did an unnecessary request to the API route to try getting a refreshed auth token)

changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 28fe8552fde7acfddc6735d407478ba5cb9bba4f

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 6 packages</summary>

  | Name                           | Type  |
| ------------------------------ | ----- |
| @keystatic/core                | Patch |
| @keystatic/test-next-app       | Patch |
| @keystatic/test-astro          | Patch |
| localization                   | Patch |
| @keystatic/test-next-pages-dir | Patch |
| @keystatic/remix-test-app      | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/Thinkmill/keystatic/new/little-fixes?filename=.changeset/silver-knives-work.md&value=---%0A%22%40keystatic%2Ftest-next-app%22%3A%20patch%0A%22%40keystatic%2Fcore%22%3A%20patch%0A---%0A%0ALittle%20fixes%0A)


vercel[bot]: [vc]: #rwmzktbPSeGXIWPt4K41gX7N+Uk1+aiAnJdm2o0jhGk=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJrZXlzdGF0aWMiLCJyb290RGlyZWN0b3J5Ijoia2V5c3RhdGljIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3RoaW5rbWlsbC9rZXlzdGF0aWMvNEhuakRneks3WFhtNU1YdlZHc0d2bVliZjV5YyIsIm5leHRDb21taXRTdGF0dXMiOiJQRU5ESU5HIn0seyJuYW1lIjoidm91c3NvaXIiLCJyb290RGlyZWN0b3J5IjoiZGVzaWduLXN5c3RlbS9kb2NzIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3RoaW5rbWlsbC92b3Vzc29pci9DOGd5ODNiQ0RTb1JIUXFUQnVpMmdpOTRuZUg4IiwicHJldmlld1VybCI6InZvdXNzb2lyLWdpdC1saXR0bGUtZml4ZXMtdGhpbmttaWxsLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated |
| :--- | :----- | :------ | :------ |
| **keystatic** | 🔄 Building ([Inspect](https://vercel.com/thinkmill/keystatic/4HnjDgzK7XXm5MXvVGsGvmYbf5yc)) |  | Mar 9, 2023 at 0:23AM (UTC) |
| **voussoir** | ✅ Ready ([Inspect](https://vercel.com/thinkmill/voussoir/C8gy83bCDSoRHQqTBui2gi94neH8)) | [Visit Preview](https://voussoir-git-little-fixes-thinkmill.vercel.app) | Mar 9, 2023 at 0:23AM (UTC) |




Patch:
diff --git a/.changeset/perfect-flowers-build.md b/.changeset/perfect-flowers-build.md
new file mode 100644
index 00000000..08e695e5
--- /dev/null
+++ b/.changeset/perfect-flowers-build.md
@@ -0,0 +1,7 @@
+---
+'@keystatic/core': patch
+---
+
+Add `description` to
+`fields.{array,checkbox,date,document,image,integer,multiselect,select,pathReference,relationship}`.
+(previously it was only on `fields.{text,slug}`)
diff --git a/.changeset/wet-socks-rest.md b/.changeset/wet-socks-rest.md
new file mode 100644
index 00000000..bec2750c
--- /dev/null
+++ b/.changeset/wet-socks-rest.md
@@ -0,0 +1,5 @@
+---
+'@keystatic/core': patch
+---
+
+Fix incorrectly attempting to get a GitHub token when using local mode
diff --git a/keystatic/local-config.tsx b/keystatic/local-config.tsx
index d54e6be8..ef42a314 100644
--- a/keystatic/local-config.tsx
+++ b/keystatic/local-config.tsx
@@ -26,6 +26,8 @@ const toneToIcon = {
   positive: checkCircle2Icon,
 };
 
+const description = 'Some description';
+
 export default config({
   storage: {
     kind: 'github',
@@ -258,5 +260,59 @@ export default config({
         logo: fields.image({ label: 'Logo' }),
       },
     }),
+    fields: singleton({
+      label: 'All Fields',
+      schema: {
+        text: fields.text({ label: 'Text', description }),
+        title: fields.slug({
+          name: { label: 'Title', description },
+          slug: { description },
+        }),
+        integer: fields.integer({ label: 'Number', description }),
+        checkbox: fields.checkbox({ label: 'Checkbox', description }),
+        select: fields.select({
+          label: 'Select',
+          description,
+          options: [
+            { value: 'one', label: 'One' },
+            { value: 'two', label: 'Two' },
+            { value: 'three', label: 'Three' },
+          ],
+          defaultValue: 'one',
+        }),
+        multiselect: fields.multiselect({
+          label: 'Multiselect',
+          description,
+          options: [
+            { value: 'one', label: 'One' },
+            { value: 'two', label: 'Two' },
+            { value: 'three', label: 'Three' },
+          ],
+          defaultValue: ['one'],
+        }),
+        array: fields.array(fields.text({ label: 'Text', description }), {
+          label: 'Array',
+          description,
+        }),
+        date: fields.date({ label: 'Date', description }),
+        document: fields.document({
+          label: 'Document',
+          description,
+          formatting: true,
+          dividers: true,
+          links: true,
+        }),
+        image: fields.image({ label: 'Image', description }),
+        pathReference: fields.pathReference({
+          label: 'Path Reference',
+          description,
+        }),
+        relationship: fields.relationship({
+          label: 'Relationship',
+          description,
+          collection: 'posts',
+        }),
+      },
+    }),
   },
 });
diff --git a/packages/keystatic/DocumentEditor/component-blocks/api.tsx b/packages/keystatic/DocumentEditor/component-blocks/api.tsx
index f2e411e2..4b213c1b 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/api.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/api.tsx
@@ -191,6 +191,7 @@ export type ArrayField<ElementField extends ComponentSchema> = {
   kind: 'array';
   element: ElementField;
   label: string;
+  description?: string;
   // this is written with unknown to avoid typescript being annoying about circularity or variance things
   itemLabel?(props: unknown): string;
   asChildTag?: string;
@@ -223,6 +224,7 @@ type ArrayFieldInComponentSchema = {
   kind: 'array';
   element: ComponentSchema;
   label: string;
+  description?: string;
   // this is written with unknown to avoid typescript being annoying about circularity or variance things
   itemLabel?(props: unknown): string;
   asChildTag?: string;
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/array.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/array.tsx
index e4e9c384..48370d30 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/array.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/array.tsx
@@ -9,6 +9,7 @@ export function array<ElementField extends ComponentSchema>(
   element: ElementField,
   opts?: {
     label?: string;
+    description?: string;
     itemLabel?: (props: GenericPreviewProps<ElementField, unknown>) => string;
     asChildTag?: string;
     slugField?: ElementField extends { kind: 'object' }
@@ -28,6 +29,7 @@ export function array<ElementField extends ComponentSchema>(
     kind: 'array',
     element,
     label: opts?.label ?? 'Items',
+    description: opts?.description,
     itemLabel: opts?.itemLabel,
     asChildTag: opts?.asChildTag,
     slugField: opts?.slugField as string,
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/checkbox.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/checkbox.tsx
index 60ab324e..eb1d9109 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/checkbox.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/checkbox.tsx
@@ -1,19 +1,23 @@
 import { Checkbox } from '@voussoir/checkbox';
+import { Text } from '@voussoir/typography';
 import { BasicFormField } from '../../../dist/keystatic-core.cjs';
 
 export function checkbox({
   label,
   defaultValue = false,
+  description,
 }: {
   label: string;
   defaultValue?: boolean;
+  description?: string;
 }): BasicFormField<boolean, undefined> {
   return {
     kind: 'form',
     Input({ value, onChange, autoFocus }) {
       return (
         <Checkbox isSelected={value} onChange={onChange} autoFocus={autoFocus}>
-          {label}
+          <Text>{label}</Text>
+          {description && <Text slot="description">{description}</Text>}
         </Checkbox>
       );
     },
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/date.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/date.tsx
index 2487b91b..987d20a0 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/date.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/date.tsx
@@ -39,10 +39,12 @@ export function date<IsRequired extends boolean | undefined>({
   label,
   defaultValue,
   validation,
+  description,
 }: {
   label: string;
   defaultValue?: string | { kind: 'today' };
   validation?: { isRequired?: IsRequired; min?: string; max?: string };
+  description?: string;
 } & RequiredValidation<IsRequired>): BasicFormField<
   string | (IsRequired extends true ? never : null),
   undefined
@@ -55,6 +57,7 @@ export function date<IsRequired extends boolean | undefined>({
       return (
         <TextField
           label={label}
+          description={description}
           type="date"
           onChange={val => {
             onChange(val === '' ? null : val);
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/document.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/document.tsx
index 1f188039..feaf643f 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/document.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/document.tsx
@@ -146,10 +146,12 @@ function normaliseDocumentFeatures(config: DocumentFeaturesConfig) {
 export function document({
   label,
   componentBlocks = {},
+  description,
   ...documentFeaturesConfig
 }: {
   label: string;
   componentBlocks?: Record<string, ComponentBlock>;
+  description?: string;
 } & DocumentFeaturesConfig): FormFieldWithFileRequiringContentsForReader<
   DocumentElement[],
   undefined,
@@ -188,7 +190,7 @@ export function document({
     defaultValue: [{ type: 'paragraph', children: [{ text: '' }] }],
     Input(props) {
       return (
-        <FieldPrimitive label={label}>
+        <FieldPrimitive label={label} description={description}>
           <DocumentEditor
             componentBlocks={componentBlocks}
             documentFeatures={documentFeatures}
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/image.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/image.tsx
index ef5d65b5..1852a2ec 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/image.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/image.tsx
@@ -3,6 +3,7 @@ import { FieldLabel, FieldMessage } from '@voussoir/field';
 import { Flex, Box } from '@voussoir/layout';
 import { tokenSchema } from '@voussoir/style';
 import { TextField } from '@voussoir/text-field';
+import { Text } from '@voussoir/typography';
 import { useState, useEffect, useReducer } from 'react';
 import { useIsInDocumentEditor } from '../..';
 import { fixPath } from '../../../app/path-utils';
@@ -67,10 +68,12 @@ export function image<IsRequired extends boolean | undefined>({
   label,
   directory,
   validation,
+  description,
 }: {
   label: string;
   directory?: string;
   validation?: { isRequired?: IsRequired };
+  description?: string;
 } & RequiredValidation<IsRequired>): FormFieldWithFileNotRequiringContentsForReader<
   | {
       kind: 'uploaded';
@@ -93,7 +96,11 @@ export function image<IsRequired extends boolean | undefined>({
       return (
         <Flex direction="column" gap="medium">
           <FieldLabel>{label}</FieldLabel>
-
+          {description && (
+            <Text size="small" color="neutralSecondary">
+              {description}
+            </Text>
+          )}
           <ButtonGroup>
             <ActionButton
               onPress={async () => {
@@ -125,7 +132,6 @@ export function image<IsRequired extends boolean | undefined>({
               </ActionButton>
             )}
           </ButtonGroup>
-
           {objectUrl && (
             <Box
               alignSelf="start"
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/integer.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/integer.tsx
index 1eb8b36b..b8878e24 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/integer.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/integer.tsx
@@ -32,10 +32,12 @@ export function integer<IsRequired extends boolean | undefined>({
   label,
   defaultValue,
   validation,
+  description,
 }: {
   label: string;
   defaultValue?: number;
   validation?: { isRequired?: IsRequired; min: number; max: number };
+  description?: string;
 } & RequiredValidation<IsRequired>): BasicFormField<
   number | (IsRequired extends true ? never : null),
   undefined
@@ -51,6 +53,7 @@ export function integer<IsRequired extends boolean | undefined>({
       return (
         <NumberField
           label={label}
+          description={description}
           errorMessage={
             forceValidation || blurred
               ? validateInteger(validation, value, label)
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/multiselect.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/multiselect.tsx
index 25e8990d..c8bdbd5b 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/multiselect.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/multiselect.tsx
@@ -1,6 +1,7 @@
 import { Checkbox } from '@voussoir/checkbox';
 import { FieldLabel } from '@voussoir/field';
 import { Flex } from '@voussoir/layout';
+import { Text } from '@voussoir/typography';
 import { useId } from 'react';
 import { BasicFormField } from '../api';
 
@@ -8,26 +9,35 @@ export function multiselect<Option extends { label: string; value: string }>({
   label,
   options,
   defaultValue = [],
+  description,
 }: {
   label: string;
   options: readonly Option[];
   defaultValue?: readonly Option['value'][];
+  description?: string;
 }): BasicFormField<readonly Option['value'][], readonly Option[]> {
   const valuesToOption = new Map(options.map(x => [x.value, x]));
   return {
     kind: 'form',
     Input({ value, onChange }) {
       const labelId = useId();
+      const descriptionId = useId();
       return (
         <Flex
           role="group"
           aria-labelledby={labelId}
+          aria-describedby={description ? descriptionId : undefined}
           direction="column"
           gap="medium"
         >
           <FieldLabel elementType="span" id={labelId}>
             {label}
           </FieldLabel>
+          {description && (
+            <Text id={descriptionId} size="small" color="neutralSecondary">
+              {description}
+            </Text>
+          )}
           {options.map(option => (
             <Checkbox
               isSelected={value.includes(option.value)}
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/pathReference.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/pathReference.tsx
index 97f0f2e7..f8ad2419 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/pathReference.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/pathReference.tsx
@@ -10,10 +10,12 @@ export function pathReference<IsRequired extends boolean | undefined>({
   label,
   pattern,
   validation,
+  description,
 }: {
   label: string;
   pattern?: string;
   validation?: { isRequired?: IsRequired };
+  description?: string;
 } & RequiredValidation<IsRequired>): BasicFormField<
   string | (IsRequired extends true ? never : null),
   undefined
@@ -47,6 +49,7 @@ export function pathReference<IsRequired extends boolean | undefined>({
       return (
         <Combobox
           label={label}
+          description={description}
           selectedKey={value}
           onSelectionChange={key => {
             if (typeof key === 'string' || key === null) {
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/relationship.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/relationship.tsx
index 5eccfaff..fe676a3b 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/relationship.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/relationship.tsx
@@ -9,10 +9,12 @@ export function relationship<IsRequired extends boolean | undefined>({
   label,
   collection,
   validation,
+  description,
 }: {
   label: string;
   collection: string;
   validation?: { isRequired?: IsRequired };
+  description?: string;
 } & RequiredValidation<IsRequired>): BasicFormField<
   string | (IsRequired extends true ? never : null),
   undefined
@@ -42,6 +44,7 @@ export function relationship<IsRequired extends boolean | undefined>({
       return (
         <Combobox
           label={label}
+          description={description}
           selectedKey={value}
           onSelectionChange={key => {
             if (typeof key === 'string' || key === null) {
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/select.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/select.tsx
index 084dce6d..5f2cc7e2 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/select.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/select.tsx
@@ -6,10 +6,12 @@ export function select<Option extends { label: string; value: string }>({
   label,
   options,
   defaultValue,
+  description,
 }: {
   label: string;
   options: readonly Option[];
   defaultValue: Option['value'];
+  description?: string;
 }): BasicFormField<Option['value'], readonly Option[]> {
   const optionValuesSet = new Set(options.map(x => x.value));
   if (!optionValuesSet.has(defaultValue)) {
@@ -23,6 +25,7 @@ export function select<Option extends { label: string; value: string }>({
       return (
         <Picker
           label={label}
+          description={description}
           items={options}
           selectedKey={value}
           onSelectionChange={key => {
diff --git a/packages/keystatic/DocumentEditor/component-blocks/fields/url.tsx b/packages/keystatic/DocumentEditor/component-blocks/fields/url.tsx
index 83ebcc20..e2a66220 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/fields/url.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/fields/url.tsx
@@ -23,10 +23,12 @@ export function url<IsRequired extends boolean | undefined>({
   label,
   defaultValue = '',
   validation,
+  description,
 }: {
   label: string;
   defaultValue?: string;
   validation?: { isRequired?: IsRequired };
+  description?: string;
 } & RequiredValidation<IsRequired>): BasicFormField<
   string | (IsRequired extends true ? never : null),
   undefined
@@ -40,6 +42,7 @@ export function url<IsRequired extends boolean | undefined>({
           width="initial"
           maxWidth={`calc(${tokenSchema.size.alias.singleLineWidth} * 3)`}
           label={label}
+          description={description}
           autoFocus={autoFocus}
           value={value === null ? '' : value}
           onChange={val => {
diff --git a/packages/keystatic/DocumentEditor/component-blocks/form-from-preview.tsx b/packages/keystatic/DocumentEditor/component-blocks/form-from-preview.tsx
index 97b0272f..5206e443 100644
--- a/packages/keystatic/DocumentEditor/component-blocks/form-from-preview.tsx
+++ b/packages/keystatic/DocumentEditor/component-blocks/form-from-preview.tsx
@@ -54,6 +54,7 @@ type DefaultFieldProps<Key> = GenericPreviewProps<
 
 function ArrayFieldPreview(props: DefaultFieldProps<'array'>) {
   const labelId = useId();
+  const descriptionId = useId();
   const stringFormatter = useLocalizedStringFormatter(l10nMessages);
 
   let onMove = (keys: Key[], target: ItemDropTarget) => {
@@ -173,11 +174,17 @@ function ArrayFieldPreview(props: DefaultFieldProps<'array'>) {
       gap="medium"
       role="group"
       aria-labelledby={labelId}
+      aria-describedby={props.schema.description ? descriptionId : undefined}
       direction="column"
     >
       <FieldLabel elementType="h3" id={labelId}>
         {props.schema.label}
       </FieldLabel>
+      {props.schema.description && (
+        <Text id={descriptionId} size="small" color="neutralSecondary">
+          {props.schema.description}
+        </Text>
+      )}
       <ActionButton
         autoFocus={props.autoFocus}
         onPress={addItem}
diff --git a/packages/keystatic/app/provider.tsx b/packages/keystatic/app/provider.tsx
index 9e8b31b0..c33b51d6 100644
--- a/packages/keystatic/app/provider.tsx
+++ b/packages/keystatic/app/provider.tsx
@@ -175,7 +175,10 @@ export default function Provider({
                         },
                       });
                     },
-                    getAuth,
+                    getAuth: async () => {
+                      if (repo) return getAuth();
+                      return null;
+                    },
                   }),
                   fetchExchange,
                 ],


Test patch:
diff --git a/packages/keystatic/markdoc/find-children.test.ts b/packages/keystatic/markdoc/find-children.test.ts
index 819192ca..57d7a933 100644
--- a/packages/keystatic/markdoc/find-children.test.ts
+++ b/packages/keystatic/markdoc/find-children.test.ts
@@ -117,6 +117,7 @@ test('array', () => {
       },
       "field": {
         "asChildTag": "related-link",
+        "description": undefined,
         "element": {
           "fields": {
             "content": {