Skip to content

Implement P0-P2 spec compliance tasks from SPEC_COMPLIANCE_EVALUATION.md - #432

Merged
hotlong merged 4 commits into
mainfrom
copilot/complete-development-tasks
Feb 10, 2026
Merged

Implement P0-P2 spec compliance tasks from SPEC_COMPLIANCE_EVALUATION.md#432
hotlong merged 4 commits into
mainfrom
copilot/complete-development-tasks

Conversation

CopilotAI commented Feb 10, 2026

Copy link
Copy Markdown
Contributor

Addresses all P0, P1, and key P2 gaps identified in the spec compliance evaluation across 12 packages.

P0 — Critical

  • DataScope module (core): New DataScopeManager implementing DataContext with row-level filtering (9 operators), read-only scopes, change listeners
  • AI plugin handlers (plugin-ai): Replaced console.log placeholders with proper callback props (onApply, onRefresh, onSelect, onDismiss, onSubmit) across AIFormAssist, AIRecommendations, NLQueryInput
  • Validators export (core): Removed stale TODO comments — already exported via validation module

P1 — High

  • DetailView/RelatedList API fetch (plugin-detail): Implemented fetch() and dataSource.find() integration with error handling
  • ReportViewer (plugin-report): Added onRefresh callback; extracted computeAggregation helper supporting count/sum/avg/min/max
  • ReportBuilder cancel (plugin-report): onCancel now actually invokes the callback
  • Map coordinates (plugin-map): Added isFinite validation + UI warning for excluded records
  • String formulas (core): FIND, REPLACE, SUBSTRING, REGEX, LEN
  • useTheme hook (react): New ThemeContext + useTheme() for component-level theme access

P2 — Medium

  • Statistical formulas (core): MEDIAN, STDEV, VARIANCE, PERCENTILE
  • DATEFORMAT (core): Token-based date formatting (YYYY/MM/DD/HH/mm/ss)
  • Custom validator registration (core): registerValidator(name, fn) / registerAsyncValidator(name, fn) on ValidationEngine
// DataScope — row-level filteringconstmanager=newDataScopeManager();manager.registerScopeWithConfig('contacts',{dataSource: myDataSource,filters: [{field: 'status',operator: 'eq',value: 'active'}],readOnly: false,});constfiltered=manager.applyFilters('contacts',records);// New formula functionsFIND('world','hello world')// 6STDEV(2,4,4,4,5,5,7,9)// ~2.138DATEFORMAT('2026-02-10','YYYY/MM')// '2026/02'

Tests

  • 35 new tests (19 DataScopeManager, 16 formula functions)
  • All 136 test files pass (2749+ tests), zero regressions
Original prompt

On SPEC_COMPLIANCE_EVALUATION.md, 按照 SPEC_COMPLIANCE_EVALUATION.md
文件的要求,完成全部开发任务


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel

vercelBot commented Feb 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
objectuiErrorErrorFeb 10, 2026 2:53pm
objectui-consoleReadyReadyPreview, CommentFeb 10, 2026 2:53pm
objectui-storybookCanceledCanceledFeb 10, 2026 2:53pm

Request Review

…rmulas, report refresh, detail API fetch, map errors, useTheme hook, validators
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ch, statistical, DATEFORMAT)
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…lify map warning message
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
CopilotAI changed the title [WIP] Complete development tasks as per SPEC_COMPLIANCE_EVALUATION.mdImplement P0-P2 spec compliance tasks from SPEC_COMPLIANCE_EVALUATION.mdFeb 10, 2026
CopilotAI requested a review from hotlongFebruary 10, 2026 14:47
@hotlong
hotlong marked this pull request as ready for review February 10, 2026 15:00
CopilotAI review requested due to automatic review settings February 10, 2026 15:00
@hotlong
hotlong merged commit 6e0aa55 into mainFeb 10, 2026
2 of 4 checks passed

CopilotAI 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

Implements multiple P0–P2 spec-compliance gaps across core and several plugins, adding missing runtime capabilities (DataScope, formulas, validator registration) and replacing placeholder UI behaviors with real callback-driven integrations.

Changes:

  • Added DataScopeManager (DataContext implementation) with filtering, read-only scopes, listeners, and exports/tests.
  • Expanded formula engine with string-search, statistical functions, and DATEFORMAT + tests.
  • Updated several plugins to wire real callbacks / data fetching paths (AI components, report viewer refresh & aggregations, detail/related list fetching, map coordinate validation).

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 10 comments.

Show a summary per file
FileDescription
packages/react/src/hooks/useTheme.tsAdds a theme hook/context in hooks layer (currently duplicates existing theme context API).
packages/react/src/hooks/index.tsRe-exports the new useTheme hook.
packages/plugin-report/src/ReportViewer.tsxAdds onRefresh prop and aggregation helper for KPI cards.
packages/plugin-report/src/ReportBuilder.tsxEnsures onCancel callback is invoked.
packages/plugin-map/src/ObjectMap.tsxFilters invalid coordinates, counts exclusions, and displays a warning banner.
packages/plugin-detail/src/RelatedList.tsxAdds DataSource integration and fetch fallback for related list data loading.
packages/plugin-detail/src/DetailView.tsxImplements detail fetching (DataSource.findOne and fetch fallback) and passes dataSource to RelatedList.
packages/plugin-ai/src/NLQueryInput.tsxAdds onSubmit callback prop and keeps simulated behavior when callback not provided.
packages/plugin-ai/src/AIRecommendations.tsxReplaces console logs with onSelect/onDismiss callback props.
packages/plugin-ai/src/AIFormAssist.tsxReplaces console logs with onApply/onRefresh callback props.
packages/core/src/validation/validation-engine.tsAdds custom validator registration (sync/async) and resolves validators by rule.type.
packages/core/src/index.tsExports the new data-scope module from core.
packages/core/src/evaluator/tests/FormulaFunctions.test.tsAdds tests for new string/stat/date functions.
packages/core/src/evaluator/FormulaFunctions.tsAdds FIND/REPLACE/SUBSTRING/REGEX/LEN, MEDIAN/STDEV/VARIANCE/PERCENTILE, and DATEFORMAT.
packages/core/src/data-scope/index.tsPublic entrypoint for data-scope exports.
packages/core/src/data-scope/tests/DataScopeManager.test.tsAdds DataScopeManager unit tests.
packages/core/src/data-scope/DataScopeManager.tsImplements DataScopeManager with filters, read-only enforcement, and listeners.
Comments suppressed due to low confidence (1)

packages/plugin-map/src/ObjectMap.tsx:424

  • This adds UI changes in the same area where the map container uses inline styles (style={{ ... }}), which is explicitly forbidden by repo conventions in .github/copilot-instructions.md:13-16. Please convert these to Tailwind classes (e.g., fixed height via h-* / min-h-* and w-full) and avoid passing style objects unless absolutely required by the library.
 <div className="relative border rounded-lg overflow-hidden bg-muted" style={{ height: '600px', width: '100%' }}>
<Map
initialViewState={initialViewState}
style={{ width: '100%', height: '100%' }}

Comment on lines 34 to +61
const [query, setQuery] = useState('');
const [result, setResult] = useState<NLQueryResult | undefined>(initialResult);
const [loading, setLoading] = useState(false);

const isLoading = loading || externalLoading;

const handleSubmit = (q?: string) => {
const queryText = q || query;
if (!queryText.trim()) return;

setLoading(true);
console.log('NL Query submitted:', queryText);
onSubmitProp?.(queryText);

// Simulate AI processing
setTimeout(() => {
setResult({
query: queryText,
summary: `Results for: "${queryText}"`,
confidence: 0.85,
data: [],
columns: [],
});
// Simulate AI processing when no external handler
if (!onSubmitProp) {
setTimeout(() => {
setResult({
query: queryText,
summary: `Results for: "${queryText}"`,
confidence: 0.85,
data: [],
columns: [],
});
setLoading(false);
}, 1000);
} else {
setLoading(false);
}, 1000);
}

CopilotAIFeb 10, 2026

Copy link

Choose a reason for hiding this comment

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

When an external onSubmit handler is provided, the component still sets internal loading true and then immediately false, which can cause a spinner flicker and doesn’t actually wait for external work. Additionally, result is initialized from schema.result once and won’t update if the parent provides a new result after onSubmit; that makes the new callback integration hard to use. Consider deriving result from schema.result (controlled) or syncing state via useEffect and using schema.loading as the source of truth when onSubmitProp is provided.

Copilot uses AI. Check for mistakes.
Comment on lines 27 to 59
@@ -33,12 +37,12 @@ export const AIFormAssist: React.FC<AIFormAssistProps> = ({ schema }) => {
const [loading, setLoading] = useState(false);

const handleApply = (suggestion: AIFieldSuggestion) => {
console.log('Apply suggestion:', suggestion);
onApply?.(suggestion);
setAppliedFields(prev => new Set(prev).add(suggestion.fieldName));
};

const handleApplyAll = () => {
console.log('Apply all suggestions:', suggestions);
suggestions.forEach(s => onApply?.(s));
setAppliedFields(new Set(suggestions.map(s => s.fieldName)));
};

@@ -48,8 +52,9 @@ export const AIFormAssist: React.FC<AIFormAssistProps> = ({ schema }) => {

const handleRefresh = () => {
setLoading(true);
console.log('Refreshing AI suggestions...');
// Simulate AI response
if (onRefresh) {
onRefresh();
}
setTimeout(() => setLoading(false), 1000);
};

CopilotAIFeb 10, 2026

Copy link

Choose a reason for hiding this comment

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

This component stores schema.suggestions into local state once (useState(initialSuggestions)) and never re-syncs when schema changes. With the new onRefresh callback, a parent is likely to update schema.suggestions, but the UI won’t reflect those updates. Consider rendering from schema.suggestions directly (controlled) or adding a useEffect to update local state when initialSuggestions changes; also consider making onRefresh async-aware instead of always clearing loading after a fixed timeout.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +48
if (dataSource) {
dataSource.find(api).then((result) => {
const items = Array.isArray(result)
? result
: Array.isArray((result as any)?.data)
? (result as any).data
: [];
setRelatedData(items);

CopilotAIFeb 10, 2026

Copy link

Choose a reason for hiding this comment

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

DataSource.find() expects a resource name and returns a QueryResult with a required .data array (packages/types/src/data.ts). Passing api (a URL string) into dataSource.find() and then treating the result as either an array or { data } is incompatible with the DataSource contract and will break for standard DataSource implementations. Consider changing this to use a resource prop (or derive the resource from schema) and read result.data from the QueryResult.

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +61
fetch(api)
.then(res => res.json())
.then(result => {
const items = Array.isArray(result) ? result : (result?.data || []);
setRelatedData(items);
})
.catch(err => {

CopilotAIFeb 10, 2026

Copy link

Choose a reason for hiding this comment

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

The fetch branch doesn’t check res.ok before calling res.json(), so non-2xx responses will be treated as successful JSON and may cause confusing runtime errors. Consider handling non-OK responses explicitly (e.g., throw with status/message) so the error state is accurate.

Copilot uses AI. Check for mistakes.
})
.finally(() => setLoading(false));
}
}, [schema.api, schema.resourceId]);

CopilotAIFeb 10, 2026

Copy link

Choose a reason for hiding this comment

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

The useEffect uses schema.data, dataSource, and schema.objectName in its logic, but the dependency array only includes schema.api and schema.resourceId. This can leave stale data/loading if dataSource or schema.data/objectName changes. Please include all referenced reactive inputs (or refactor) so the effect re-runs correctly.

Suggested change
},[schema.api,schema.resourceId]);
},[schema.api,schema.resourceId,schema.data,schema.objectName,dataSource]);

Copilot uses AI. Check for mistakes.
Comment on lines 56 to +66
} else if (schema.api && schema.resourceId) {
setLoading(true);
// TODO: Fetch from API
// This would integrate with the data provider
setTimeout(() => {
setLoading(false);
}, 500);
fetch(`${schema.api}/${schema.resourceId}`)
.then(res => res.json())
.then(result => {
setData(result?.data || result);
})
.catch(err => {
console.error('Failed to fetch detail data:', err);
})
.finally(() => setLoading(false));

CopilotAIFeb 10, 2026

Copy link

Choose a reason for hiding this comment

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

Similar to RelatedList, this introduces a raw fetch() path inside a UI component. Project guidance prefers routing data access through the DataSource abstraction for backend-agnostic behavior and consistent auth/error handling (.github/copilot-instructions.md:183-185). Consider using a DataSource-based implementation (or injecting a RestDataSource) instead of calling fetch directly here.

Copilot uses AI. Check for mistakes.
Comment on lines +205 to +212
return format
.replace('YYYY', String(date.getFullYear()))
.replace('YY', String(date.getFullYear()).slice(-2))
.replace('MM', pad(date.getMonth() + 1))
.replace('DD', pad(date.getDate()))
.replace('HH', pad(date.getHours()))
.replace('mm', pad(date.getMinutes()))
.replace('ss', pad(date.getSeconds()));

CopilotAIFeb 10, 2026

Copy link

Choose a reason for hiding this comment

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

DATEFORMAT uses String.prototype.replace for each token, which only replaces the first occurrence. Formats that repeat tokens (e.g., 'YYYY/MM/DD HH:mm:ss [YYYY]') won’t be formatted correctly. Consider using a single global token-regex replacement or per-token global replacements so all occurrences are handled.

Suggested change
returnformat
.replace('YYYY',String(date.getFullYear()))
.replace('YY',String(date.getFullYear()).slice(-2))
.replace('MM',pad(date.getMonth()+1))
.replace('DD',pad(date.getDate()))
.replace('HH',pad(date.getHours()))
.replace('mm',pad(date.getMinutes()))
.replace('ss',pad(date.getSeconds()));
consttokenRegex=/YYYY|YY|MM|DD|HH|mm|ss/g;
returnformat.replace(tokenRegex,(token)=>{
switch(token){
case'YYYY':
returnString(date.getFullYear());
case'YY':
returnString(date.getFullYear()).slice(-2);
case'MM':
returnpad(date.getMonth()+1);
case'DD':
returnpad(date.getDate());
case'HH':
returnpad(date.getHours());
case'mm':
returnpad(date.getMinutes());
case'ss':
returnpad(date.getSeconds());
default:
returntoken;
}
});

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +59
import { createContext, useContext } from 'react';
import type { Theme, ThemeMode } from '@object-ui/types';

/**
* Theme context value for component-level theme access.
*/
export interface ThemeContextValue {
/** Current active theme */
theme: Theme | null;
/** Current theme mode */
mode: ThemeMode;
/** Set the theme mode */
setMode: (mode: ThemeMode) => void;
/** Set a new theme */
setTheme: (theme: Theme) => void;
/** Get a resolved CSS variable value */
getCssVar: (name: string) => string;
}

const defaultThemeContext: ThemeContextValue = {
theme: null,
mode: 'auto',
setMode: () => {},
setTheme: () => {},
getCssVar: (name: string) => {
if (typeof document !== 'undefined') {
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
}
return '';
},
};

export const ThemeContext = createContext<ThemeContextValue>(defaultThemeContext);

ThemeContext.displayName = 'ThemeContext';

/**
* Hook for component-level theme access.
*
* Provides the current theme, mode, and utility functions
* for reading and modifying theme state.
*
* @example
* ```tsx
* const { theme, mode, setMode, getCssVar } = useTheme();
* const primaryColor = getCssVar('--primary');
* ```
*/
export function useTheme(): ThemeContextValue {
return useContext(ThemeContext);
}

CopilotAIFeb 10, 2026

Copy link

Choose a reason for hiding this comment

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

This file introduces a new ThemeContext/useTheme that conflicts with the existing theme system in packages/react/src/context/ThemeContext.tsx (which already exports ThemeProvider + useTheme and throws when missing provider). Because packages/react/src/index.ts re-exports both './hooks' and './context', this will create duplicate/ambiguous exports (e.g., two different useTheme definitions) and inconsistent ThemeContextValue shapes. Consider removing this hook, or re-exporting the existing context hook instead of defining a second ThemeContext here.

Suggested change
import{createContext,useContext}from'react';
importtype{Theme,ThemeMode}from'@object-ui/types';
/**
*Themecontextvalueforcomponent-levelthemeaccess.
*/
exportinterfaceThemeContextValue{
/** Current active theme */
theme: Theme|null;
/** Current theme mode */
mode: ThemeMode;
/** Set the theme mode */
setMode: (mode: ThemeMode)=>void;
/** Set a new theme */
setTheme: (theme: Theme)=>void;
/** Get a resolved CSS variable value */
getCssVar: (name: string)=>string;
}
constdefaultThemeContext: ThemeContextValue={
theme: null,
mode: 'auto',
setMode: ()=>{},
setTheme: ()=>{},
getCssVar: (name: string)=>{
if(typeofdocument!=='undefined'){
returngetComputedStyle(document.documentElement).getPropertyValue(name).trim();
}
return'';
},
};
exportconstThemeContext=createContext<ThemeContextValue>(defaultThemeContext);
ThemeContext.displayName='ThemeContext';
/**
*Hookforcomponent-levelthemeaccess.
*
*Providesthecurrenttheme,mode,andutilityfunctions
*forreadingandmodifyingthemestate.
*
* @example
*```tsx
*const{ theme, mode, setMode, getCssVar }=useTheme();
*constprimaryColor=getCssVar('--primary');
*```
*/
exportfunctionuseTheme(): ThemeContextValue{
returnuseContext(ThemeContext);
}
// Re-export the canonical theme hook from the context theme system
export{useTheme}from'../context/ThemeContext';

Copilot uses AI. Check for mistakes.
Comment on lines +16 to 17
export * from './useTheme';

CopilotAIFeb 10, 2026

Copy link

Choose a reason for hiding this comment

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

Exporting useTheme from hooks here will collide with the existing useTheme export from packages/react/src/context/ThemeContext.tsx when consumers import from '@object-ui/react' (root index re-exports both hooks and context). This is likely to cause TS/ESM duplicate export errors or confusing API resolution. Prefer re-exporting only one useTheme (ideally from the existing ThemeProvider context).

Suggested change
export * from './useTheme';

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +64
dataSource.find(api).then((result) => {
const items = Array.isArray(result)
? result
: Array.isArray((result as any)?.data)
? (result as any).data
: [];
setRelatedData(items);
setLoading(false);
}).catch((err) => {
console.error('Failed to fetch related data:', err);
setLoading(false);
});
} else {
fetch(api)
.then(res => res.json())
.then(result => {
const items = Array.isArray(result) ? result : (result?.data || []);
setRelatedData(items);
})
.catch(err => {
console.error('Failed to fetch related data:', err);
})
.finally(() => setLoading(false));

CopilotAIFeb 10, 2026

Copy link

Choose a reason for hiding this comment

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

This component now performs raw fetch() calls as a fallback when no DataSource is provided. Repo guidance explicitly discourages using raw fetch in components and prefers going through the DataSource abstraction to remain backend-agnostic (.github/copilot-instructions.md:183-185). Consider requiring a DataSource prop (or providing a RestDataSource adapter) instead of embedding fetch logic here.

Suggested change
dataSource.find(api).then((result)=>{
constitems=Array.isArray(result)
? result
: Array.isArray((resultasany)?.data)
? (resultasany).data
: [];
setRelatedData(items);
setLoading(false);
}).catch((err)=>{
console.error('Failed to fetch related data:',err);
setLoading(false);
});
}else{
fetch(api)
.then(res=>res.json())
.then(result=>{
constitems=Array.isArray(result) ? result : (result?.data||[]);
setRelatedData(items);
})
.catch(err=>{
console.error('Failed to fetch related data:',err);
})
.finally(()=>setLoading(false));
dataSource
.find(api)
.then((result)=>{
constitems=Array.isArray(result)
? result
: Array.isArray((resultasany)?.data)
? (resultasany).data
: [];
setRelatedData(items);
setLoading(false);
})
.catch((err)=>{
console.error('Failed to fetch related data:',err);
setLoading(false);
});
}else{
console.error(
'RelatedList: "api" was provided but no dataSource is available. Remote loading is disabled for this component.'
);
setLoading(false);

Copilot uses AI. Check for mistakes.
xuyushun441-sys added a commit that referenced this pull request Jun 23, 2026
…offline state (#1917)
* fix(chatbot): collapse a built plan's "Build it" to an inert Built badge
issue #432: after a build completed, the earlier "Proposed plan" card kept
its active "Build it" button; clicking it re-sent the approval and rebuilt
the whole app. A plan is now "built" once an `apply_blueprint` invocation
appears after it in the conversation (keyed on toolName, so it survives a
reload that strips the rich draftReview output). When built, the actions
collapse to a static "Built" badge — no re-trigger. A later, not-yet-built
plan (e.g. after "make it simpler") keeps its live button.
Adds planBuiltLabel (default "Built"), wired + localized (zh "已搭建") in
AiChatPage and ConsoleFloatingChatbot. ChatbotEnhanced tests: 44 pass (2 new).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(chatbot): visible always-on liveness timer + offline/stall states
issue #432 follow-up. The loading feedback was too faint to see, had no
visible countdown before the first server byte, and never surfaced a real
network drop. Now:
- LivenessIndicator shows the live m:ss elapsed timer in EVERY tier (waiting/
receiving/stalled), not only once bytes arrive — the turn never looks frozen.
- New 'offline' tier driven by navigator.onLine + online/offline events: a
network drop shows a red WifiOff "Connection lost — reconnecting…" instantly,
instead of waiting for the stream-quiet timeout.
- A running tool ("Propose blueprint · Running") and the build progress panel
now show the same live timer + offline cue (ToolRunningTimer), so long
blueprint/build calls have a visible countdown.
Adds connectionStalledLabel/connectionOfflineLabel (localized zh in
ConsoleFloatingChatbot + AiChatPage). ChatbotEnhanced tests: 47 pass (4 new:
always-on timer, offline tier, running-summary timer, updated stall format).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jun 27, 2026
…lier design wait (#2036)
Two UX fixes for the AI Build chat (plugin-chatbot + the two surfaces that
drive it, AiChatPage and ConsoleFloatingChatbot).
1. propose_blueprint always yields an explicit confirm gate.
The "Proposed plan" card (with Build it / Adjust) only renders when the tool
result parses into a structured `proposedPlan` (status:'blueprint_proposed'
with ≥1 nameable object). When propose_blueprint finished but returned a
thin/oddly-shaped envelope — or proposed in prose — `detectProposedPlan`
returned undefined, the step collapsed into a "Propose blueprint · Completed"
chip, and the user was left with only the assistant's text telling them to
reply "确认" (guess-the-phrase, no button). Now an unstructured-but-finished
proposal routes to the detailed body and renders a FALLBACK confirm card with
the same explicit Build it / Adjust buttons and the same #432 built-state
collapse. The rich card still wins whenever the plan parses (no double card).
2. A friendlier "we're designing" wait for the long propose_blueprint call.
propose_blueprint is one atomic LLM request (no token stream), so the bare
elapsed timer felt stuck. While it runs, the summary strip (and the detailed
header) now show a "Designing your app…" lead-in plus a hint that rotates
every 3.5s (mapping data → shaping objects → connecting records → planning
views → pulling it together), beside the live timer — so the wait visibly
moves. Rotation is presentational only (not real sub-step progress) and is
fully localizable; an empty hints array disables it.
All copy is label-driven (English defaults in plugin-chatbot; zh/en wired in
both surfaces). plugin-chatbot tests 133 → 142 (fallback routing/button/built
collapse + hint rotation). app-shell 938 tests green. Both packages type-check
and build (DTS) clean.
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 17, 2026
…iately (#2632)
Staging E2E (2026-07-17): clicking 开始搭建 (plan approve) showed no change
at the card for ~10s — the approval sends a chat message whose visible
effects (user bubble + streaming turn) land at the BOTTOM of the thread,
outside the viewport when the card is in view — so users assumed the click
was lost and clicked again (#2627).
- Track approved plan ids locally; the clicked card's buttons flip to a
spinning "Building…" badge on click (both the structured plan card and
the unstructured fallback gate). Built state still derives from the
message stream (#432 semantics unchanged).
- An approval that never left the client (unsent error) rolls the badge
back so the button returns; a newer typed/suggestion send supersedes the
approve as "last send" so ITS failure can't roll back a delivered
approval.
- New planBuildingLabel prop; AiChatPage passes 正在搭建… for zh
conversations.
Partially addresses #2627 — the conversation-history-clears-after-build
race needs a live repro and stays open.
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 17, 2026
* fix(auth): login-page config race + sign-in watchdog — never strand SSO-only users on a password wall
Staging E2E (2026-07-17) on a freshly provisioned environment: the login
page's FIRST load rendered the plain password form — no "Continue with
ObjectStack", no ssoEnforced collapse — because the /auth/config fetch hung
or failed while the kernel cold-started, and both LoginForm and
SocialSignInButtons silently fell back to defaults. Platform-SSO JIT users
have no password, so this dead-ends the "open your environment" moment
(#2625). Clicking the SSO button inside the same cold-start window hung the
POST forever with the button stuck spinning (#2626).
- getConfig: single-flight + success cache (3 requests → 1) with retrying
backoff (500ms/1.5s/3.5s, 8s per-attempt AbortController timeout) so a
hung request converts into a retry; final failure clears the cache.
- LoginForm: hold a spinner until config resolves; on resolve, honour
ssoEnforced on first paint. On final failure keep the old safe default
(password form) — break-glass beats lock-out.
- signInWithProvider: 20s watchdog rejects a hung sign-in so the #2458
button contract (pending + inline error) can recover it; legacy oauth2
fallback failures no longer mask the social-route error.
- Drop LoginForm's duplicate "or" divider (SocialSignInButtons already
renders one) — the stacked dividers read as a glitch.
Closes#2625. Closes#2626.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(app-shell,components): welcome CTA deep-links into the environment create dialog
Staging E2E (2026-07-17): the welcome hero's "Create your environment"
navigated to the environments list, where the user had to find and click a
SECOND create button — an extra hop on the very first thing a new user does
(#844).
- action:button: client-side `autoTrigger` flag — runs the action once on
mount through the exact same execute path as a click (param dialog,
confirm, entitlement gate all apply). Not persisted metadata; only client-
composed schemas set it.
- EnvironmentListToolbar: consume `?runAction=create_environment` once
entitlements resolve — setup_production / add_development mark the create
action autoTrigger; upgrade-locked orgs open the upgrade prompt (the
honest answer to "create" there). Param is stripped on consumption so
refresh / back don't re-open the dialog. Router-free (location +
replaceState) so non-Router hosts and tests keep working.
- CloudOnboardingNext: the create CTA navigates with the runAction param.
- i18n: the toolbar's state-aware label overrides were hard-coded English
in a zh console — now {en,zh} via the same pick() pattern as the widget.
Closes#844.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(chatbot): plan approval flips the card to a Building… badge immediately
Staging E2E (2026-07-17): clicking 开始搭建 (plan approve) showed no change
at the card for ~10s — the approval sends a chat message whose visible
effects (user bubble + streaming turn) land at the BOTTOM of the thread,
outside the viewport when the card is in view — so users assumed the click
was lost and clicked again (#2627).
- Track approved plan ids locally; the clicked card's buttons flip to a
spinning "Building…" badge on click (both the structured plan card and
the unstructured fallback gate). Built state still derives from the
message stream (#432 semantics unchanged).
- An approval that never left the client (unsent error) rolls the badge
back so the button returns; a newer typed/suggestion send supersedes the
approve as "last send" so ITS failure can't roll back a delivered
approval.
- New planBuildingLabel prop; AiChatPage passes 正在搭建… for zh
conversations.
Partially addresses #2627 — the conversation-history-clears-after-build
race needs a live repro and stays open.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(auth): drop RegisterForm's duplicate "or" divider (register page had the same glitch as login)
Live staging verification (2026-07-17, cloud.objectos.app/_console/register)
caught that #2629 fixed the double divider on the LOGIN page but the SIGN-UP
page still stacked "OR CONTINUE WITH EMAIL" (from SocialSignInButtons) over a
second "OR" (from RegisterForm). Remove RegisterForm's redundant AuthDivider —
same fix as LoginForm — and clean up the now-unused hasSocialProviders state.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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.

3 participants

@hotlong