diff --git a/src/components/agents/config/ConfigFlagsSummary.vue b/src/components/agents/config/ConfigFlagsSummary.vue
new file mode 100644
index 00000000..c3d3773e
--- /dev/null
+++ b/src/components/agents/config/ConfigFlagsSummary.vue
@@ -0,0 +1,83 @@
+
+
+
+ Flags
+
+
+
+ {{
+ row.label
+ }}
+ {{ row.value }}
+
+
+
+
+
+
+
diff --git a/src/components/agents/config/effective/AccessIcon.vue b/src/components/agents/config/effective/AccessIcon.vue
new file mode 100644
index 00000000..065347fb
--- /dev/null
+++ b/src/components/agents/config/effective/AccessIcon.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
diff --git a/src/components/agents/config/effective/EditableField.vue b/src/components/agents/config/effective/EditableField.vue
new file mode 100644
index 00000000..c3179cd9
--- /dev/null
+++ b/src/components/agents/config/effective/EditableField.vue
@@ -0,0 +1,135 @@
+
+
+
+
+
diff --git a/src/components/agents/config/effective/InlineScalarEditor.vue b/src/components/agents/config/effective/InlineScalarEditor.vue
new file mode 100644
index 00000000..63a85bd8
--- /dev/null
+++ b/src/components/agents/config/effective/InlineScalarEditor.vue
@@ -0,0 +1,250 @@
+
+
+
+
+
diff --git a/src/components/agents/config/effective/MapFieldEditor.vue b/src/components/agents/config/effective/MapFieldEditor.vue
new file mode 100644
index 00000000..482c5480
--- /dev/null
+++ b/src/components/agents/config/effective/MapFieldEditor.vue
@@ -0,0 +1,83 @@
+
+ draft.set(p(k), v)"
+ @reset="(k) => draft.unset(p(k))"
+ @delete="(k) => draft.makeAbsent(p(k))"
+ />
+
+
+
+
diff --git a/src/components/agents/config/effective/PendingPill.vue b/src/components/agents/config/effective/PendingPill.vue
new file mode 100644
index 00000000..16d16d47
--- /dev/null
+++ b/src/components/agents/config/effective/PendingPill.vue
@@ -0,0 +1,33 @@
+
+
+ pending: {{ text }}
+
+
+
+
+
diff --git a/src/components/agents/config/effective/__tests__/emptyValue.spec.ts b/src/components/agents/config/effective/__tests__/emptyValue.spec.ts
new file mode 100644
index 00000000..49a440f9
--- /dev/null
+++ b/src/components/agents/config/effective/__tests__/emptyValue.spec.ts
@@ -0,0 +1,34 @@
+import { describe, expect, it, vi } from 'vitest';
+import type { ConfigDoc } from '@/types/agent-config';
+import { definedInAnyBase, setStringKey } from '../emptyValue';
+
+const PTR = '/plugins/p/config/k';
+const withKey = { plugins: { p: { config: { k: 'file' } } } } as ConfigDoc;
+const without = { plugins: { p: { config: {} } } } as ConfigDoc;
+
+function fakeDraft() {
+ return { set: vi.fn(), unset: vi.fn() };
+}
+
+describe('emptied string keys', () => {
+ it('knows whether any base defines the key', () => {
+ expect(definedInAnyBase([null, without, withKey], PTR)).toBe(true);
+ expect(definedInAnyBase([null, without], PTR)).toBe(false);
+ });
+
+ it('empty unsets a key some file defines', () => {
+ const d = fakeDraft();
+ setStringKey(d, [without, withKey], PTR, '');
+ expect(d.unset).toHaveBeenCalledWith(PTR);
+ expect(d.set).not.toHaveBeenCalled();
+ });
+
+ it('empty keeps "" for an overlay-only key; a value is always set', () => {
+ const d = fakeDraft();
+ setStringKey(d, [without], PTR, '');
+ expect(d.set).toHaveBeenCalledWith(PTR, '');
+ setStringKey(d, [withKey], PTR, 'v');
+ expect(d.set).toHaveBeenCalledWith(PTR, 'v');
+ expect(d.unset).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/components/agents/config/effective/emptyValue.ts b/src/components/agents/config/effective/emptyValue.ts
new file mode 100644
index 00000000..90942d4c
--- /dev/null
+++ b/src/components/agents/config/effective/emptyValue.ts
@@ -0,0 +1,26 @@
+// The "emptied value" rule shared by the inline editors of string map keys (plugin config
+// and labels): emptying a key that any instance's file defines inherits the file value again
+// (the overlay drops the key); a key only the overlay adds keeps an empty string (its Remove /
+// delete action removes it).
+import type { OverlayDraft } from '@/composables/agent-config/useOverlayDraft';
+import type { ConfigDoc } from '@/types/agent-config';
+import { hasAt } from '@/utils/agent-config/json-pointer';
+
+/** Whether any of `bases` (the instances' files) defines a value at `ptr`. */
+export function definedInAnyBase(
+ bases: readonly (ConfigDoc | null | undefined)[],
+ ptr: string,
+): boolean {
+ return bases.some((b) => !!b && hasAt(b, ptr));
+}
+
+/** Writes `value` at `ptr`, applying the empty-value rule above. */
+export function setStringKey(
+ draft: Pick,
+ bases: readonly (ConfigDoc | null | undefined)[],
+ ptr: string,
+ value: string,
+): void {
+ if (!value && definedInAnyBase(bases, ptr)) draft.unset(ptr);
+ else draft.set(ptr, value);
+}
diff --git a/src/components/agents/config/effective/nullKeys.ts b/src/components/agents/config/effective/nullKeys.ts
new file mode 100644
index 00000000..42b9a8b0
--- /dev/null
+++ b/src/components/agents/config/effective/nullKeys.ts
@@ -0,0 +1,27 @@
+// policy_data is stored as an RFC 7396 merge patch, where `null` at an object key means
+// "delete the key": a null value can't be stored for a key. Items of an array may be null (an
+// array is written whole).
+import { isPlainObject } from '@/utils/agent-config/merge-patch';
+import type { PatchOp } from '@/utils/agent-config/policy-data-patch';
+
+export const NULL_KEY_ERROR =
+ "null can't be stored for a key (it means delete in an overlay)";
+
+/** The dotted path of the first object key holding null in `v` (outside arrays), or null. */
+export function nullKeyPath(v: unknown, path: string[] = []): string | null {
+ if (!isPlainObject(v)) return null;
+ for (const [k, child] of Object.entries(v)) {
+ if (child === null) return [...path, k].join('.');
+ const found = nullKeyPath(child, [...path, k]);
+ if (found !== null) return found;
+ }
+ return null;
+}
+
+/** Whether `ops` (key-level edits) would write null for some key. */
+export function opsSetNullKey(ops: readonly PatchOp[]): boolean {
+ return ops.some(
+ (o) =>
+ o.op === 'set' && (o.value === null || nullKeyPath(o.value) !== null),
+ );
+}
diff --git a/src/components/agents/config/effective/scalar.ts b/src/components/agents/config/effective/scalar.ts
new file mode 100644
index 00000000..03a3fd1d
--- /dev/null
+++ b/src/components/agents/config/effective/scalar.ts
@@ -0,0 +1,8 @@
+// Kinds of inline scalar editors on the Effective view (R69).
+export type ScalarKind =
+ | 'text'
+ | 'cron'
+ | 'duration'
+ | 'bool'
+ | 'verbosity'
+ | 'protocol';