From d46fb36229a214a142e32605335c3a525adb18fb Mon Sep 17 00:00:00 2001 From: Fadhlan Ridhwanallah Date: Thu, 19 Feb 2026 21:55:02 +0700 Subject: [PATCH 1/3] Should not be able to edit file def instance in code mode --- .../components/operator-mode/code-submode.gts | 51 ++++++++++++++++++- .../code-submode/file-def-navigation-test.gts | 15 ++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/host/app/components/operator-mode/code-submode.gts b/packages/host/app/components/operator-mode/code-submode.gts index f150be5fea..5109dbf7c2 100644 --- a/packages/host/app/components/operator-mode/code-submode.gts +++ b/packages/host/app/components/operator-mode/code-submode.gts @@ -16,9 +16,11 @@ import perform from 'ember-concurrency/helpers/perform'; import FromElseWhere from 'ember-elsewhere/components/from-elsewhere'; import { consume, provide } from 'ember-provide-consume-context'; +import { use, resource } from 'ember-resources'; import window from 'ember-window-mock'; import startCase from 'lodash/startCase'; +import { TrackedObject } from 'tracked-built-ins'; import { LoadingIndicator, @@ -30,6 +32,7 @@ import { File } from '@cardstack/boxel-ui/icons'; import type { CodeRef } from '@cardstack/runtime-common'; import { isCardDocumentString, + isCardErrorJSONAPI, RealmPaths, PermissionsContextName, GetCardContextName, @@ -57,8 +60,10 @@ import type PlaygroundPanelService from '@cardstack/host/services/playground-pan import type RealmService from '@cardstack/host/services/realm'; import type RecentFilesService from '@cardstack/host/services/recent-files-service'; import type SpecPanelService from '@cardstack/host/services/spec-panel-service'; +import type StoreService from '@cardstack/host/services/store'; import type { + BaseDef, CardDef, Format, CardContext, @@ -143,6 +148,7 @@ export default class CodeSubmode extends Component { @service declare private recentFilesService: RecentFilesService; @service declare private realm: RealmService; @service declare private specPanelService: SpecPanelService; + @service declare private store: StoreService; @tracked private loadFileError: string | null = null; @tracked private userHasDismissedURLError = false; @@ -594,8 +600,51 @@ export default class CodeSubmode extends Component { this.operatorModeStateService.updateCardPreviewFormat(format); } + private get isNonModuleFile() { + return !this.isModule && !isCardDocumentString(this.readyFile.content); + } + + @use private fileDefResource = resource(() => { + let state = new TrackedObject<{ + value: BaseDef | undefined; + isLoading: boolean; + error: unknown; + }>({ + value: undefined, + isLoading: false, + error: undefined, + }); + if (!this.isNonModuleFile) { + return state; + } + let fileUrl = this.readyFile.url; + state.isLoading = true; + (async () => { + try { + let result = await this.store.get(fileUrl, { type: 'file-meta' }); + if (isCardErrorJSONAPI(result)) { + state.error = result; + state.value = undefined; + } else { + state.value = result as unknown as BaseDef; + state.error = undefined; + } + } catch (e) { + state.error = e; + state.value = undefined; + } finally { + state.isLoading = false; + } + })(); + return state; + }); + + private get isFileDefInstance() { + return this.fileDefResource?.value !== undefined; + } + get isReadOnly() { - return !this.realm.canWrite(this.readyFile.url); + return !this.realm.canWrite(this.readyFile.url) || this.isFileDefInstance; } @provide(PermissionsContextName) diff --git a/packages/host/tests/acceptance/code-submode/file-def-navigation-test.gts b/packages/host/tests/acceptance/code-submode/file-def-navigation-test.gts index 1fb90dd1e6..c4d5064672 100644 --- a/packages/host/tests/acceptance/code-submode/file-def-navigation-test.gts +++ b/packages/host/tests/acceptance/code-submode/file-def-navigation-test.gts @@ -128,4 +128,19 @@ Some markdown content.`, assert.dom('[data-test-card-url-bar-input]').hasValue(expectedMarkdownUrl); }); + + test('file def instance is read-only in code mode', async function (assert) { + await visitOperatorMode({ + submode: 'code', + codePath: `${testRealmURL}FileLinkCard/notes.md`, + }); + + await waitFor('[data-test-editor]'); + assert + .dom('[data-test-format-chooser="edit"]') + .doesNotExist('edit format option is not shown for file def instance'); + assert + .dom('[data-test-realm-indicator-not-writable]') + .exists('read-only indicator is shown for file def instance'); + }); }); From d951883be9500e2e789389623dc5d6301ae4716a Mon Sep 17 00:00:00 2001 From: FadhlanR Date: Mon, 23 Feb 2026 23:36:58 +0700 Subject: [PATCH 2/3] Default false when loading fileDef instance Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/host/app/components/operator-mode/code-submode.gts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/host/app/components/operator-mode/code-submode.gts b/packages/host/app/components/operator-mode/code-submode.gts index 5109dbf7c2..33608bffd4 100644 --- a/packages/host/app/components/operator-mode/code-submode.gts +++ b/packages/host/app/components/operator-mode/code-submode.gts @@ -644,7 +644,11 @@ export default class CodeSubmode extends Component { } get isReadOnly() { - return !this.realm.canWrite(this.readyFile.url) || this.isFileDefInstance; + return ( + !this.realm.canWrite(this.readyFile.url) || + this.isFileDefInstance || + this.fileDefResource?.isLoading + ); } @provide(PermissionsContextName) From 431628e76a4b8da72676cc5d0179abd2393992ee Mon Sep 17 00:00:00 2001 From: Fadhlan Ridhwanallah Date: Mon, 23 Feb 2026 23:39:33 +0700 Subject: [PATCH 3/3] reuse isFileDefInstance function --- packages/host/app/components/operator-mode/code-submode.gts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/host/app/components/operator-mode/code-submode.gts b/packages/host/app/components/operator-mode/code-submode.gts index 33608bffd4..278b421e26 100644 --- a/packages/host/app/components/operator-mode/code-submode.gts +++ b/packages/host/app/components/operator-mode/code-submode.gts @@ -39,6 +39,7 @@ import { type ResolvedCodeRef, type getCard, CardContextName, + isFileDefInstance, } from '@cardstack/runtime-common'; import { isEquivalentBodyPosition } from '@cardstack/runtime-common/schema-analysis-plugin'; @@ -640,7 +641,9 @@ export default class CodeSubmode extends Component { }); private get isFileDefInstance() { - return this.fileDefResource?.value !== undefined; + return ( + this.fileDefResource && isFileDefInstance(this.fileDefResource.value) + ); } get isReadOnly() {