feat(FN-3156): add database schema init hook runner for plugin settings lif

The merge adds plugin schema initialization lifecycle support (FN-3156), introduces a ResearchTaskActionModal with model fallback notifications (FN-3012, FN-3008), adds async planning draft sync to prevent UI blocking (FN-3229), and implements viewport-conditional mission split layout (FN-3130). Inf

Fusion-Task-Id: FN-3156
This commit is contained in:
Fusion
2026-05-02 20:25:46 -07:00
committed by gsxdsm
parent c6119f78ef
commit d1cb401a0d
11 changed files with 237 additions and 8 deletions

View File

@@ -201,6 +201,15 @@ export function MobileNavBar({
const primaryPluginViews = pluginDashboardViews
.filter((entry) => entry.view.placement === "primary")
.sort((a, b) => (a.view.order ?? Number.MAX_SAFE_INTEGER) - (b.view.order ?? Number.MAX_SAFE_INTEGER));
// Keep plugin-provided top-level tabs constrained on mobile so fixed tabs retain
// reasonable touch-target width. Additional primary plugin destinations overflow into More.
const MAX_PRIMARY_PLUGIN_TOP_LEVEL_TABS = 1;
const topLevelPrimaryPluginViews = primaryPluginViews.slice(0, MAX_PRIMARY_PLUGIN_TOP_LEVEL_TABS);
const overflowPrimaryPluginViews = primaryPluginViews.slice(MAX_PRIMARY_PLUGIN_TOP_LEVEL_TABS);
const overflowPluginViews = [
...overflowPrimaryPluginViews,
...pluginDashboardViews.filter((entry) => entry.view.placement !== "primary"),
].sort((a, b) => (a.view.order ?? Number.MAX_SAFE_INTEGER) - (b.view.order ?? Number.MAX_SAFE_INTEGER));
const isMoreActive =
view === "documents"
@@ -212,7 +221,7 @@ export function MobileNavBar({
|| (todosOpen && todoViewEnabled)
|| (view === "roadmaps" && !showRoadmapsTopLevel)
|| (view === "skills" && !showSkillsTopLevel)
|| (view.startsWith("plugin:") && !primaryPluginViews.some((entry) => buildPluginTaskViewId(entry.pluginId, entry.view.viewId) === view));
|| (view.startsWith("plugin:") && !topLevelPrimaryPluginViews.some((entry) => buildPluginTaskViewId(entry.pluginId, entry.view.viewId) === view));
return (
<>
@@ -320,7 +329,7 @@ export function MobileNavBar({
</button>
)}
{primaryPluginViews.map((entry) => {
{topLevelPrimaryPluginViews.map((entry) => {
const pluginTaskView = buildPluginTaskViewId(entry.pluginId, entry.view.viewId);
const PluginIcon = getPluginNavIcon(entry.view.icon);
return (
@@ -660,10 +669,7 @@ export function MobileNavBar({
</button>
)}
{pluginDashboardViews
.filter((entry) => entry.view.placement !== "primary")
.sort((a, b) => (a.view.order ?? Number.MAX_SAFE_INTEGER) - (b.view.order ?? Number.MAX_SAFE_INTEGER))
.map((entry) => {
{overflowPluginViews.map((entry) => {
const pluginTaskView = buildPluginTaskViewId(entry.pluginId, entry.view.viewId);
const PluginIcon = getPluginNavIcon(entry.view.icon);
return (

View File

@@ -134,6 +134,30 @@ describe("MobileNavBar", () => {
expect(props.onChangeView).toHaveBeenCalledWith("plugin:fusion-plugin-dependency-graph:queue");
});
it("limits primary plugin tabs on mobile and overflows extra primary views into More", () => {
render(
<MobileNavBar
{...createDefaultProps()}
pluginDashboardViews={[
{
pluginId: "fusion-plugin-dependency-graph",
view: { viewId: "graph", label: "Graph", componentPath: "./GraphView", icon: "Map", placement: "primary", order: 1 },
},
{
pluginId: "fusion-plugin-dependency-graph",
view: { viewId: "queue", label: "Queue", componentPath: "./QueueView", icon: "Workflow", placement: "primary", order: 2 },
},
]}
/>,
);
expect(screen.getByTestId("mobile-nav-tab-plugin-fusion-plugin-dependency-graph-graph")).toBeDefined();
expect(screen.queryByTestId("mobile-nav-tab-plugin-fusion-plugin-dependency-graph-queue")).toBeNull();
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
expect(screen.getByTestId("mobile-more-item-plugin-fusion-plugin-dependency-graph-queue")).toBeDefined();
});
it("active tab is highlighted for mailbox", () => {
render(<MobileNavBar {...createDefaultProps()} view="mailbox" />);
expect(screen.getByTestId("mobile-nav-tab-mailbox").className).toContain("mobile-nav-tab--active");