feat(uat-batch): Groups J + K — activity feed + onboarding resolver-chain
J38, J39, K40 (core) from the 2026-05-21 plan.
Shipped:
J38 EntityActivityFeed sentence rendering surfaces the new value
inline. Was "<actor> updated the X"; now "<actor> set X to
<value>" when the audit row carries `newValue`. Field-level
diff line underneath keeps showing the old → new strikethrough
for context. Truncates inline value at 60 chars to keep long
notes / descriptions from blowing out the row.
J39 Client → Companies tab CTA. Empty state gains a "Link to a
company" action; populated state grows a top-right "Link to
company" button. New <LinkCompanyDialog> wraps the existing
<CompanyPicker> + a membership-role select + an "is primary"
checkbox, then POSTs to /api/v1/companies/[id]/members.
Empty-state copy dropped "Add a membership from a company's
detail page" — the rep can act inline now.
K40 OnboardingChecklist resolver-chain. The auto-check no longer
reads raw `/admin/settings` rows (which miss env fallbacks).
Resolved endpoint widened to accept `?keys=k1,k2,...` so the
checklist can batch-resolve any heterogenous set of registry
keys through port → global → env → default in one round-trip.
Checklist captures the dominant source per step ("env fallback",
"global default", "built-in default") and surfaces it inline
under the green tick so super-admins see when a step is
relying on env rather than a per-port override. Compound-key
gates report the weakest sub-key's source so a partially-env
config still flags clearly.
Topbar banner / dashboard tile / weekly nudge / celebration
sub-items remain queued — the core resolver-chain gap was
the actual cause of the "step never ticks" UAT complaint.
Verified: tsc clean, vitest 1454/1454.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,16 +2,20 @@ import { NextResponse } from 'next/server';
|
||||
|
||||
import { withAuth, withPermission } from '@/lib/api/helpers';
|
||||
import { errorResponse } from '@/lib/errors';
|
||||
import { entriesForSections } from '@/lib/settings/registry';
|
||||
import { entriesForSections, registryFor } from '@/lib/settings/registry';
|
||||
import { resolveForAdminAPI } from '@/lib/settings/resolver';
|
||||
|
||||
/**
|
||||
* GET /api/v1/admin/settings/resolved?sections=documenso.api,documenso.signers
|
||||
* GET /api/v1/admin/settings/resolved?keys=branding_logo_url,smtp_host_override
|
||||
*
|
||||
* Returns the resolved value + source (port/global/env/default) for every
|
||||
* registry entry in the requested sections. Drives the registry-driven
|
||||
* admin form: the `source` field gates the "Using env fallback" badge.
|
||||
* requested registry entry. Drives both the registry-driven admin form
|
||||
* (sections param) and the onboarding-checklist auto-detection (keys
|
||||
* param) — both need port→global→env→default resolution rather than the
|
||||
* raw `/admin/settings` rows (which only show DB writes).
|
||||
*
|
||||
* Either parameter is supported; if both are present the sets union.
|
||||
* Sensitive fields surface `isSet` only — never the decrypted value.
|
||||
*/
|
||||
export const GET = withAuth(
|
||||
@@ -19,14 +23,33 @@ export const GET = withAuth(
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const sectionsParam = url.searchParams.get('sections');
|
||||
if (!sectionsParam) {
|
||||
const keysParam = url.searchParams.get('keys');
|
||||
if (!sectionsParam && !keysParam) {
|
||||
return NextResponse.json({ data: { entries: [], values: {} } }, { status: 200 });
|
||||
}
|
||||
const sections = sectionsParam
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
const entries = entriesForSections(sections);
|
||||
? sectionsParam
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const extraKeys = keysParam
|
||||
? keysParam
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const sectionEntries = entriesForSections(sections);
|
||||
const keyEntries = extraKeys
|
||||
.map((k) => registryFor(k))
|
||||
.filter((e): e is NonNullable<typeof e> => Boolean(e));
|
||||
// Dedupe by `key` so section + key overlap doesn't double-resolve.
|
||||
const seen = new Set<string>();
|
||||
const entries = [...sectionEntries, ...keyEntries].filter((e) => {
|
||||
if (seen.has(e.key)) return false;
|
||||
seen.add(e.key);
|
||||
return true;
|
||||
});
|
||||
const keys = entries.map((e) => e.key);
|
||||
const resolved = await resolveForAdminAPI(keys, ctx.portId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user