Skip to content

feat: Index fields - #7382

Open
demshy wants to merge 68 commits into
decaporg:mainfrom
poslovnimediji:feat/index-fields
Open

feat: Index fields#7382
demshy wants to merge 68 commits into
decaporg:mainfrom
poslovnimediji:feat/index-fields

Conversation

@demshy

@demshy demshy commented Jan 29, 2025

Copy link
Copy Markdown
Member

Closes #7381.

Folder collections can define a separate field set and editor configuration for their index file, so a collection can hold both regular entries and the page that describes the collection itself — a blog's posts plus its list page, a section's children plus its landing page.

A new index_file option on folder collections:

key
pattern required regular expression matched against the entry slug
fields optional field set for matching entries; falls back to the collection's fields
editor.preview optional toggles the preview pane for matching entries independently of the collection
collections:
  - name: posts
    label: Posts
    folder: _posts
    create: true
    index_file:
      pattern: _index
      editor:
        preview: false
      fields:
        - { label: Title, name: title, widget: string }
        - { label: Body, name: body, widget: markdown }
    fields:
      - { label: Title, name: title, widget: string }
      - { label: Publish Date, name: date, widget: datetime }
      - { label: Body, name: body, widget: markdown }

Entries matching the pattern get the index field set, are marked with a home icon in the entry list, and are sorted to the top of the collection.

Nested collections

In a nested collection the feature also lets one collection mix index pages and content pages. The "New" button becomes a dropdown offering Index Page or Content Page, and the choice is carried through as a read-only path_type meta field that decides where the entry is written:

  - name: sections
    label: Sections
    folder: content/sections
    create: true
    nested: { depth: 3, subfolders: false }
    index_file:
      pattern: '^_index$'
      fields:
        - { label: Title, name: title, widget: string }
        - { label: Intro, name: intro, widget: text, required: false }
    fields:
      - { label: Title, name: title, widget: string }
      - { label: Body, name: body, widget: markdown }
    # index_file.pattern decides which entries get the index fields;
    # meta.path.index_file decides the filename an index entry is written to.
    # Both are required for nested collections, and they have to agree.
    meta: { path: { widget: string, label: Path, index_file: '_index' } }

An index page at path guides/advanced is written to guides/advanced/_index.md; a content page at guides/first-steps is written to guides/first-steps.md.

i18n

Deleting an entry that is missing some of its translations used to fail with API_ERROR: GitRPC::BadObjectState: the CMS asked the backend to remove a file for every configured locale, and GitHub rejects the whole commit when a path is not in the tree.

Entries now report only the locales they were actually loaded with, and only those files are deleted. Two things had to change for that: getI18nEntry kept every fulfilled read, and the github, gitea and forgejo backends end getEntry with .catch(() => ({ …, data: '' })) — it resolves for a missing file, so a locale with no file arrived looking like an empty translation. gitlab, bitbucket and azure reject, which is why this only reproduced on GitHub and git-gateway.

Backends

Path handling for index vs. content entries is implemented for github, gitlab, bitbucket, azure, gitea, proxy/decap-server and test. forgejo landed after this branch started and has not been updated.

Docs: decaporg/decap-website#157


Test plan

Unit and e2e coverage: cypress/e2e/index_fields_spec.js covers sort order, the index icon, index vs. collection field sets, preview on/off, an edit round trip, and the nested path-type flow against the posts and sections collections in dev-test. New unit tests cover getExistingFilePaths, getI18nEntry and the config schema.

Manually verified on a Netlify deploy preview of this branch, against a real repository over git-gateway, with i18n: { structure: multiple_files, locales: [en, de, si] }:

  • index entries render the index field set, content entries render the collection's
  • preview pane suppressed for index entries only, including newly created ones
  • Index Page writes <path>/_index.md, Content Page writes <path>.md
  • deleting an entry present in one locale deletes exactly that one file
  • deleting a fully translated entry still deletes all of its locale files

Status

  • update and uncomment path validation
  • config structure and terminology settled (index_file with pattern / fields /
    editor)
  • updating other backends
  • updating tests

Known limitations

  • An index file is sorted to the top of the entries already loaded, and the collection view pages in 20 entries at a time. In a collection large enough that the index file is not on the first page, it will not appear at the top until it has been paged in.
  • A nested collection needs both index_file.pattern and meta.path.index_file, and nothing validates that the two agree.
  • Converting an index file into a content file or vice versa is not supported. Remove the file and add a new one with the desired slug.
  • An i18n entry whose default locale file is missing still reports the default locale path, because the merged entry normalises its path to the default locale.

@demshy
demshy force-pushed the feat/index-fields branch from cb61157 to db6b1b0 Compare February 6, 2025 09:00
@martinjagodic
martinjagodic deleted the feat/index-fields branch June 5, 2025 09:22
@demshy
demshy restored the feat/index-fields branch June 5, 2025 11:37
@martinjagodic
martinjagodic requested a review from a team as a code owner December 4, 2025 14:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds support for index files with separate field configurations in folder collections, addressing issue #7381. The feature allows users to differentiate between index files (like _index.md) and regular content files within the same collection, each with their own field definitions.

Changes:

  • Adds index_file configuration option to collection schemas with pattern matching, custom fields, and editor settings
  • Implements path type selection dropdown for nested collections to choose between creating index pages or content pages
  • Updates backend implementations (GitHub, local Git, local FS) to handle index file identification and folder-aware file operations

Reviewed changes

Copilot reviewed 35 out of 36 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
packages/decap-cms-core/src/types/redux.ts Adds TypeScript types for index_file config, path_type meta field, readonly field property, and CmsCollectionMeta interface
packages/decap-cms-core/index.d.ts Updates public TypeScript definitions with index_file configuration support
packages/decap-cms-core/src/constants/configSchema.js Adds schema validation for index_file configuration including pattern validation and file collection restriction
packages/decap-cms-core/src/constants/tests/configSchema.spec.js Adds comprehensive tests for index_file validation rules
packages/decap-cms-core/src/lib/indexFileHelper.ts Creates new utility functions for identifying index files and entries with nested collection support
packages/decap-cms-core/src/reducers/collections.ts Updates field selection logic to return index_file fields when appropriate and adds nested collection helpers
packages/decap-cms-core/src/reducers/entryDraft.js Modifies custom path calculation to handle both index and slug path types
packages/decap-cms-core/src/reducers/entries.ts Adds logic to ensure index file entries appear first in the entry list
packages/decap-cms-core/src/reducers/tests/entries.spec.js Adds tests for index_file entry loading and path_type meta field handling
packages/decap-cms-core/src/backend.ts Updates entry loading to set path_type meta field and implements folder-aware file operations
packages/decap-cms-core/src/actions/entries.ts Updates path validation to check both index and slug path types, fixes duplicate entry action
packages/decap-cms-core/src/actions/config.ts Adds meta field injection for path and path_type fields in collections with index_file
packages/decap-cms-core/src/actions/tests/entries.spec.js Updates test expectations for path_type in meta field validation
packages/decap-cms-core/src/lib/i18n.ts Adds meta field removal from i18n data, updates file path comparison for nested collections
packages/decap-cms-core/src/lib/tests/i18n.spec.js Adds mock for isNestedSubfolders selector
packages/decap-cms-core/src/components/Collection/CollectionTop.js Adds dropdown for path type selection in nested collections with index_file
packages/decap-cms-core/src/components/Collection/NestedCollection.js Uses centralized isNestedSubfolders helper
packages/decap-cms-core/src/components/Collection/Entries/EntryCard.js Adds home icon indicator for index file entries
packages/decap-cms-core/src/components/Editor/EditorInterface.js Updates preview detection to respect index_file editor settings
packages/decap-cms-core/src/components/Editor/EditorControlPane/EditorControl.js Adds readonly field support to disable editing
packages/decap-cms-core/src/components/Editor/Editor.js Passes path_type query param to field selection and fixes nested collection back link calculation
packages/decap-server/src/middlewares/utils/fs.ts Adds isFolder parameter to move function to conditionally move child files
packages/decap-server/src/middlewares/types.ts Adds isFolder property to DataFile type
packages/decap-server/src/middlewares/localGit/index.ts Passes isFolder parameter to move function
packages/decap-server/src/middlewares/localFs/index.ts Passes isFolder parameter to move function
packages/decap-cms-lib-util/src/implementation.ts Adds isFolder property to DataFile type
packages/decap-cms-backend-github/src/API.ts Updates tree operations to handle folder-aware file moves
packages/decap-cms-locales/src/en/index.js Adds English translations for path type labels
packages/decap-cms-locales/src/sl/index.js Adds Slovenian translations for path type labels
packages/decap-cms-core/src/tests/backend.spec.js Updates test expectation to include path_type in meta
packages/decap-cms-core/src/valueObjects/Entry.ts Adds path_type to meta object type and srcSlug to EntryValue
dev-test/config.yml Adds index_file configuration example
dev-test/index.html Adds _index.md test file data
dev-test/backends/test/config.yml Adds index_file configuration example
dev-test/backends/test/index.html Adds _index.md test file data
cypress/e2e/index_fields_spec.js Adds E2E tests for index file feature

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

usedSlugs,
customPath,
);
isFolder = prepareMetaPathType(slug, collection) === 'index';

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

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

The condition pathType === 'index' means the code determines isFolder = true for index file entries and isFolder = false for slug-type entries. However, this naming is confusing because an "index file" is still a file, not a folder. The variable name isFolder suggests it represents a directory, but it actually represents whether the entry should be treated as having subfolders that need to be moved along with it. Consider renaming this to something more descriptive like moveSubfolders or hasSubfolders to clarify its purpose.

Copilot uses AI. Check for mistakes.
data: entry.get('data'),
i18n: entry.get('i18n'),
meta: entry.get('meta').toJS(),
i18n: entry.get('i18n').toJS(),

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

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

The draftDuplicateEntry function is calling .toJS() on entry.get('i18n') which may be undefined. If the entry doesn't have i18n data, this will throw a TypeError. You should add a check to ensure i18n exists before calling .toJS(), or provide a fallback value like an empty object.

Copilot uses AI. Check for mistakes.
payload: createEntry(entry.get('collection'), '', '', {
data: entry.get('data'),
i18n: entry.get('i18n'),
meta: entry.get('meta').toJS(),

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

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

Similarly, calling .toJS() on entry.get('meta') may fail if meta is undefined. While meta is more commonly present, it's safer to add a check or provide a fallback value to avoid potential runtime errors.

Suggested change
meta: entry.get('meta').toJS(),
meta: (entry.get('meta') || Map()).toJS(),

Copilot uses AI. Check for mistakes.
.get('fields')
?.find(f => f.get('name') === inferredFields.imageField && f.get('widget') === 'image'),
isLoadingAsset,
showIndexFileIcon: indexFileConfig && new RegExp(indexFileConfig.get('pattern')).test(fileSlug),

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

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

The pattern matching logic doesn't consider the nested flag from the collection. The isIndexFile helper function accounts for nested collections by extracting just the filename from the slug when nested is true. However, here you're testing the entire fileSlug directly against the pattern without considering whether the collection is nested. This could lead to inconsistent behavior where a file is identified as an index file in some parts of the code but not others.

Consider using the isIndexFile helper function here instead of duplicating the pattern matching logic, or ensure the slug extraction logic is consistent with what's done in the helper.

Copilot uses AI. Check for mistakes.
}
const meta = entryDraft.getIn(['entry', 'meta']);
const path = meta && meta.get('path');
const pathType = meta && meta.get('path_type', 'index');

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

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

The check condition seems backwards. When pathType === 'index' the default should be 'index', not 'slug'. Similarly, when checking for nested subfolders or index path type, both should use the index file name. The current logic may be correct but would benefit from clarification. Consider restructuring to make the intent clearer:

const pathType = meta && meta.get('path_type', 'slug'); // default to 'slug' for regular files

This makes it clearer that 'slug' is the default for content files, while 'index' is explicitly set for index files.

Suggested change
const pathType = meta && meta.get('path_type', 'index');
const pathType = meta && meta.get('path_type', 'slug'); // default to 'slug' for regular files

Copilot uses AI. Check for mistakes.
Comment on lines +1028 to +1030
path_type: string,
) {
const customPath = selectCustomPath(collection, fromJS({ entry: { meta: { path, path_type } } }));

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

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

The function signature shows path_type using snake_case naming, which is inconsistent with JavaScript/TypeScript conventions where camelCase is typically used for variable names. Consider renaming this parameter to pathType for consistency with the codebase's naming conventions.

Suggested change
path_type: string,
) {
const customPath = selectCustomPath(collection, fromJS({ entry: { meta: { path, path_type } } }));
pathType: string,
) {
const customPath = selectCustomPath(
collection,
fromJS({ entry: { meta: { path, path_type: pathType } } }),
);

Copilot uses AI. Check for mistakes.
Comment on lines +1028 to +1030
path_type: string,
) {
const customPath = selectCustomPath(collection, fromJS({ entry: { meta: { path, path_type } } }));

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

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

The variable name uses snake_case (path_type) which is inconsistent with JavaScript/TypeScript naming conventions. While this matches the data property name, for local variables camelCase (pathType) is more conventional in JavaScript/TypeScript code.

Suggested change
path_type: string,
) {
const customPath = selectCustomPath(collection, fromJS({ entry: { meta: { path, path_type } } }));
pathType: string,
) {
const customPath = selectCustomPath(
collection,
fromJS({ entry: { meta: { path, path_type: pathType } } }),
);

Copilot uses AI. Check for mistakes.
@@ -443,7 +447,7 @@ function mapStateToProps(state, ownProps) {
if (collection.has('nested') && slug) {

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

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

The path calculation logic has changed from slice(0, -2) to slice(0, -1), but there's no accompanying test or comment explaining why this change is necessary. This appears to be fixing an off-by-one error in the back link calculation for nested collections, but without context it's unclear if this is correct for all nested collection scenarios. Consider adding a comment explaining the logic or ensuring test coverage validates this change works correctly for different path depths.

Suggested change
if (collection.has('nested') && slug) {
if (collection.has('nested') && slug) {
// For nested collections, the slug is a path-like string:
// <folder>/<subfolder>/.../<entrySlug>
// We want the back link to point to the parent folder view, not the entry itself.
// Therefore, we drop only the final entry segment with slice(0, -1) and keep
// all leading folders, which are then used as the filter path.

Copilot uses AI. Check for mistakes.
Comment thread packages/decap-cms-core/src/lib/i18n.ts Outdated
Comment on lines +145 to +148
if (!data || typeof data !== 'object' || !('delete' in data)) {
return data;
}
return (data as Map<string, unknown>).delete('path').delete('path_type');

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

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

The removeMetaFields function has a fragile type check. The condition !('delete' in data) will return true for any object that doesn't have a 'delete' property, not just for Immutable.js Map objects. This could lead to unexpected behavior if non-Map objects are passed to this function. Consider using a more specific check like Map.isMap(data) to verify that the data is actually an Immutable.js Map before attempting to call the delete method.

Suggested change
if (!data || typeof data !== 'object' || !('delete' in data)) {
return data;
}
return (data as Map<string, unknown>).delete('path').delete('path_type');
if (!Map.isMap(data)) {
return data;
}
return data.delete('path').delete('path_type');

Copilot uses AI. Check for mistakes.
const [p1, p2] = [path1, path2].map(p => p.split('/'));
return hasSubfolders
? p1.slice(-2).join('/') === p2.slice(-2).join('/')
: p1.at(-1) === p2.at(-1);

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

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

The array at() method is used here, but it's a relatively recent addition to JavaScript (ES2022). While the code may work in modern environments, this could cause issues in older browsers or Node.js versions that don't support this method. Consider using traditional array indexing instead: p1[p1.length - 1] === p2[p2.length - 1] for better compatibility, or ensure your build configuration properly transpiles this feature.

Suggested change
: p1.at(-1) === p2.at(-1);
: p1[p1.length - 1] === p2[p2.length - 1];

Copilot uses AI. Check for mistakes.
const pathParts = slug.split('/');
if (pathParts.length > 2) {
editorBackLink = `${editorBackLink}/filter/${pathParts.slice(0, -2).join('/')}`;
editorBackLink = `${editorBackLink}/filter/${pathParts.slice(0, -1).join('/')}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this change be applied to all nested collections or only ones using index fields? Just flagging in case that's a possible regression.

.get('fields')
?.find(f => f.get('name') === inferredFields.imageField && f.get('widget') === 'image'),
isLoadingAsset,
showIndexFileIcon: indexFileConfig && new RegExp(indexFileConfig.get('pattern')).test(fileSlug),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe this should use the isIndexFile helper instead?

Comment on lines +82 to +86
@@ -83,6 +83,7 @@ export function localFsMiddleware({ repoPath, logger }: FsOptions) {
await move(
path.join(repoPath, dataFile.path),
path.join(repoPath, dataFile.newPath!),
dataFile.isFolder,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

GitHub is weird with suggestions outside the lines changed, but AFAIK, forEach doesn't await properly, so this should be an for loop instead.

            dataFiles.forEach(async dataFile => {
              await move(
                path.join(repoPath, dataFile.path),
                path.join(repoPath, dataFile.newPath!),
                dataFile.isFolder,
              );
            });

Comment on lines 99 to 105
dataFiles.forEach(async dataFile => {
await move(path.join(repoPath, dataFile.path), path.join(repoPath, dataFile.newPath!));
await move(
path.join(repoPath, dataFile.path),
path.join(repoPath, dataFile.newPath!),
dataFile.isFolder,
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, it should be a for loop

@yanthomasdev

Copy link
Copy Markdown
Contributor

Added docs in decaporg/decap-website#157

yanthomasdev and others added 2 commits July 9, 2026 17:29
Resolved conflicts from the pnpm migration and the entry-move refactor:

- dev-test config.yml (x2): kept main's removal of the `draft` view group
  and its Relation Test group, kept our `index_file` block.
- packages/decap-cms-backend-gitea/package.json: adopted main's pnpm
  catalog/workspace protocol, keeping `path-browserify` (now `catalog:`)
  and the `browser` path alias.
- package-lock.json: deleted, superseded by pnpm-lock.yaml.
- decap-cms-core index.d.ts / types/redux.ts / constants/configSchema.js:
  additive — kept both our `index_file` and main's `limit`.
- backend-test implementation.ts: kept main's `moveFile` helper and
  passed the combined flag (`hasSubfolders && isFolder !== false`), so
  only folder-type index entries drag their subfolder contents along.
  Folded in our guard that skips rewriting the moved entry itself, since
  the caller writes it fresh at its new path.
- decap-server utils/fs.ts `move()`: kept main's repo-boundary path
  resolution and added our optional `isFolder` param; children are moved
  using the resolved paths.
- decap-server localFs/localGit: call `move()` with main's `repoPath`
  signature plus `dataFile.isFolder`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented Sep 3, 2026

Copy link
Copy Markdown

Deploy Preview for decap-cms ready!

Name Link
🔨 Latest commit f5adf49
🔍 Latest deploy log https://app.netlify.com/projects/decap-cms/deploys/6a994df70dc3f30008b2f99e
😎 Deploy Preview https://deploy-preview-7382--decap-cms.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

martinjagodic and others added 6 commits September 3, 2026 11:32
- await entry moves sequentially in the local fs/git middlewares; the
  `forEach(async ...)` did not await, so the response was sent and the
  commit was created before the moves had finished
- use the app's shared history in CollectionTop instead of a second,
  unsynchronised createHashHistory() instance, and drop the leftover `t`
  argument at the getCollectionProps call site
- guard the meta/i18n `.toJS()` calls in draftDuplicateEntry
- check `Map.isMap` in removeMetaFields rather than sniffing for a
  `delete` property
- rename the `path_type` parameter of getExistingEntry to `pathType`
- explain what `srcSlug` is for at both sites that touch it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two behaviour changes leaked into collections that do not use the
feature, both caught by the existing editorial workflow e2e spec:

- prepareMetaPath returned the raw slug for any nested collection with
  `subfolders: false` whose entry was not index-type. With no
  `index_file` configured every entry is slug-type, so the meta path
  widget showed `directory/index` instead of `directory`. Gate the new
  branch on `index_file`.
- the editor back link dropped one path segment instead of two for every
  nested collection. An index-file entry is addressed by the folder it
  lives in, but a legacy nested entry is addressed by the folder it
  represents, so it has to drop that folder too. Pick the number of
  segments from whether the collection uses index files.

Also fix `editor.preview` not applying to a newly created index page:
isPreviewEnabled matched on the slug, which a new entry does not have
yet. Use the isIndexFileEntry helper, which consults `meta.path_type`
first, as the review suggested for the duplicated pattern matching.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
index_fields_spec asserted on UI text that does not exist ('New Post',
and an editor-only 'Writing in' link clicked from the collection page),
so 3 of its 6 tests failed; the 3 that passed did not exercise index
files at all. Rewrite it against the real UI: sort order, the index
icon, index vs collection field sets, preview on/off, an edit round
trip, and the nested path type flow.

The `_posts/_index.md` fixture this feature added gives the posts
collection a 24th entry with no date, which the view filter and view
group specs still counted as 23 and grouped into 2 buckets. Update
those expectations, including the new `missing_value` group.

Add a `sections` collection to the dev-test and test-backend configs so
the nested path type dropdown can be exercised at all. It needs both
`index_file.pattern` and `meta.path.index_file`: the first picks the
fields, the second picks the filename an index entry is written to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deleting a partially translated entry still failed with
`API_ERROR: GitRPC::BadObjectState`, reproduced against a real
git-gateway/GitHub repo on the branch deploy: the request asked GitHub to
remove all three locale paths, two of which are not in the tree, and the
whole commit is rejected.

The existing guard probed `implementation.getEntry` for each path and
dropped the ones that threw, but the GitHub, Gitea and Forgejo backends
end `getEntry` with `.catch(() => ({ ..., data: '' }))`, so it resolves
for a missing file and nothing was ever filtered out. It also cost one
request per locale.

Derive the paths from the entry instead: a merged i18n entry's `i18n` map
holds every locale it was loaded with apart from the one its own path
points at, so no extra requests are needed. Unknown entries keep the old
behaviour of targeting every configured locale.

Known gap: an entry whose default locale file is missing still reports
the default locale path, because the merged entry normalises its path to
the default locale. That is the same case `srcSlug` works around.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dex file

The collection view loads 20 entries at a time and hoists the index file
to the top of the entries already loaded. dev-test's posts collection has
24 entries and the index file sorts last, so it is not on the first page
at all and none of the posts assertions could see it. Scroll the whole
collection in first.

Also fix the boolean group count in view_groups_spec: the index file has
no relation_test field either, so it joins that grouping's missing_value
bucket too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tion

The previous commit derived the delete paths from the entry's loaded
locales, but the entry itself was wrong: `getI18nEntry` keeps every
fulfilled read, and the GitHub, Gitea and Forgejo backends end `getEntry`
with `.catch(() => ({ ..., data: '' }))`, so a locale with no file
resolves and looks like an empty translation. gitlab, bitbucket and azure
reject instead, which is why this only bites on GitHub and git-gateway.

Drop reads that came back with empty raw content, so a partially
translated entry reports only the locales it actually has. Verified
against a real git-gateway repo on the branch deploy.

Also fix the paths helper: `mergeValues` omits the `i18n` key entirely
when an entry has no locale other than its own, so an absent key means
"default locale only", not "unknown".

Co-Authored-By: Claude Opus 5 (1M context) <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.

Enable index files editing in same folder as single pages

4 participants