Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **TUI goal creation result screen**: Cockpit and Goals-screen authoring now show a bounded pending, success, or failure result after submission. Successful results include the created goal ID and remain visible until Enter is pressed; failures retain every entered value for retry or allow cancellation with Escape.

### Fixed

- **TUI goal scope authoring**: Scope-in and scope-out values are now collected as independent repeatable items, preserving spaces, item order, and entered values when navigating backward or forward through the goal wizard.
- **TUI goal authoring layout**: Long objective values now wrap within the bounded goal wizard without expanding its background into horizontal bands, and the input cursor remains after the final wrapped character.

## [3.20.0] - 2026-07-25

### Added
Expand Down Expand Up @@ -812,7 +821,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **Goal update extended**: 'goal update' extended with '--nextGoalId' for latent goal chaining.


## [1.0.0-beta.0] - 2026-01-01

### Changed
Expand Down
4 changes: 2 additions & 2 deletions docs/getting-started/first-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Select option 2 to auto-approve subsequent registrations of the same type, or re
End your agent session when finished, or clear context and switch to a fresh terminal for Step 3.

:::tip[Best practice]
Archive all of your context related `.md` files to a location outside of your repository, and strip your `AGENTS.md` of any special instructions that can be registered with Jumbo. They will cause context bloat and potential conflicting instructions (as your project evolves) if left for agents to read. *You can always ask an agent to port them from Jumbo to an `.md` later if you want to remove Jumbo.*
Archive all of your context related `.md` files to a location outside of your repository, and strip your `AGENTS.md` of any special instructions that can be registered with Jumbo. They will cause context bloat and potential conflicting instructions (as your project evolves) if left for agents to read. _You can always ask an agent to port them from Jumbo to an `.md` later if you want to remove Jumbo._
:::

---
Expand All @@ -133,7 +133,7 @@ jumbo goal add \
"Includes unit tests for limit exceeded scenarios"
```

If you are using the TUI, run `jumbo` and press `g` from the primed-empty Cockpit screen to open the same goal authoring flow without leaving the interface.
If you are using the TUI, run `jumbo` and press `g` from the primed-empty Cockpit screen to open the same goal authoring flow without leaving the interface. After submission, the final screen shows the request status and created goal ID; press Enter to acknowledge success, or retry or cancel if creation fails.

You can also press `m` anywhere the main TUI frame owns input to open the Mega Menu. Use the arrow keys and Enter to navigate between Cockpit, Goals, Memory, and Settings. Goal menu entries such as Backlog, Active, and Archive open the Goals screen with the matching status filter already applied.

Expand Down
96 changes: 54 additions & 42 deletions src/presentation/tui/application-shell/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import { SearchOverlay } from "../search/SearchOverlay.js";
import { InitFlow } from "../project-initialization/InitFlow.js";
import type { InitFlowActionControllers } from "../project-initialization/InitFlow.js";
import { GoalAuthoringFlow } from "../goals/GoalAuthoringFlow.js";
import type { GoalAuthoringValues } from "../goals/GoalAuthoringFlow.js";
import type {
GoalAuthoringSubmissionResult,
GoalAuthoringValues,
} from "../goals/GoalAuthoringFlow.js";
import { GoalAuthoringRequestStatus } from "../goals/GoalAuthoringFlowConstants.js";
import { AddGoalRequestFactory } from "../goals/AddGoalRequestFactory.js";
import { DEFAULT_SCREEN_INDEX } from "../navigation/ScreenDefinitions.js";
import { ActionDispatcher } from "../action-dispatch/ActionDispatcher.js";
Expand All @@ -20,7 +24,10 @@ import type { StateReaderOptions } from "../state-reading/StateReaderOptions.js"
import { useProjectContext } from "../state-reading/useProjectContext.js";
import { SubprocessManagerProvider } from "../daemon-subprocesses/SubprocessManagerProvider.js";
import { useSubprocessManager } from "../daemon-subprocesses/useSubprocessManager.js";
import type { ISubprocessManager, SubprocessSnapshot } from "../daemon-subprocesses/ISubprocessManager.js";
import type {
ISubprocessManager,
SubprocessSnapshot,
} from "../daemon-subprocesses/ISubprocessManager.js";
import type { NotificationDrawerNotification } from "./NotificationDrawer.js";
import type { CliUpdateController } from "../../../application/cli-metadata/update/CliUpdateController.js";
import type { CliUpdateCheckResult } from "../../../application/cli-metadata/update/CliUpdateCheckResult.js";
Expand Down Expand Up @@ -187,19 +194,17 @@ function AppFrame({
const subprocessManager = useSubprocessManager();
const { columns, rows } = useTerminalDimensions();
const projectContext = useProjectContext();
const [activeScreenIndex, setActiveScreenIndex] = useState(
DEFAULT_SCREEN_INDEX,
);
const [activeScreenIndex, setActiveScreenIndex] =
useState(DEFAULT_SCREEN_INDEX);
const [initFlowOpen, setInitFlowOpen] = useState(false);
const [goalAuthoringOpen, setGoalAuthoringOpen] = useState(false);
const [searchOpen, setSearchOpen] = useState(false);
const [megaMenuOpen, setMegaMenuOpen] = useState(false);
const [notificationDrawerOpen, setNotificationDrawerOpen] = useState(false);
const [screenModalOpen, setScreenModalOpen] = useState(false);
const [goalStatusFilter, setGoalStatusFilter] =
useState<readonly GoalStatusType[] | undefined>(undefined);
const [goalAuthoringError, setGoalAuthoringError] = useState<string | null>(null);
const [goalAuthoringWorking, setGoalAuthoringWorking] = useState(false);
const [goalStatusFilter, setGoalStatusFilter] = useState<
readonly GoalStatusType[] | undefined
>(undefined);
const [lifecycleRouteOverride, setLifecycleRouteOverride] =
useState<ProjectLifecycleState | null>(null);
const [unprimedSkipped, setUnprimedSkipped] = useState(false);
Expand All @@ -209,9 +214,9 @@ function AppFrame({
const [billboardAnimationComplete, setBillboardAnimationComplete] = useState(
!launchAnimationEnabled,
);
const [daemonStatuses, setDaemonStatuses] = useState<readonly SubprocessSnapshot[]>(
subprocessManager.getAllStatuses(),
);
const [daemonStatuses, setDaemonStatuses] = useState<
readonly SubprocessSnapshot[]
>(subprocessManager.getAllStatuses());
const [cliUpdateCheck, setCliUpdateCheck] =
useState<CliUpdateCheckResult | null>(null);
const [cliUpgradeResult, setCliUpgradeResult] =
Expand Down Expand Up @@ -338,7 +343,6 @@ function AppFrame({
setInitFlowOpen(true);
}
if (goalAuthoringShortcutEnabled && (input === "g" || input === "G")) {
setGoalAuthoringError(null);
setGoalAuthoringOpen(true);
}
if (
Expand Down Expand Up @@ -379,10 +383,9 @@ function AppFrame({

const handleInitComplete = useCallback(
async (_values: Record<string, string>) => {
await completeStateChangingOverlay(
() => setInitFlowOpen(false),
{ reinstallStateReaders: true },
);
await completeStateChangingOverlay(() => setInitFlowOpen(false), {
reinstallStateReaders: true,
});
},
[completeStateChangingOverlay],
);
Expand All @@ -392,36 +395,47 @@ function AppFrame({
}, []);

const handleGoalAuthoringComplete = useCallback(
async (values: GoalAuthoringValues) => {
async (
values: GoalAuthoringValues,
): Promise<GoalAuthoringSubmissionResult> => {
const addGoalController = actionControllers?.addGoalController;
if (addGoalController === undefined) {
setGoalAuthoringError(AppCopy.goalAuthoringUnavailable);
return;
return {
status: GoalAuthoringRequestStatus.FAILURE,
error: AppCopy.goalAuthoringUnavailable,
};
}

setGoalAuthoringWorking(true);
setGoalAuthoringError(null);
const result = await ActionDispatcher.dispatch(
addGoalController,
AddGoalRequestFactory.create(values),
);
setGoalAuthoringWorking(false);

if (!result.ok) {
setGoalAuthoringError(result.error.message);
return;
return {
status: GoalAuthoringRequestStatus.FAILURE,
error: result.error.message,
};
}

await completeStateChangingOverlay(
() => setGoalAuthoringOpen(false),
{ lifecycleRouteOverride: ProjectLifecycle.PRIMED },
);
return {
status: GoalAuthoringRequestStatus.SUCCESS,
goalId: result.response.goalId,
};
},
[actionControllers, completeStateChangingOverlay],
[actionControllers],
);

const handleGoalAuthoringAcknowledged = useCallback(
async (_goalId: string) => {
await completeStateChangingOverlay(() => setGoalAuthoringOpen(false), {
lifecycleRouteOverride: ProjectLifecycle.PRIMED,
});
},
[completeStateChangingOverlay],
);

const handleGoalAuthoringCancel = useCallback(() => {
setGoalAuthoringError(null);
setGoalAuthoringOpen(false);
}, []);

Expand All @@ -444,11 +458,7 @@ function AppFrame({
setCliUpgradeResult(result);
setCliUpgradeWorking(false);
},
[
cliUpdateCheck,
cliUpdateController,
cliUpgradeWorking,
],
[cliUpdateCheck, cliUpdateController, cliUpgradeWorking],
);

const handleBannerAnimationComplete = useCallback(() => {
Expand All @@ -472,7 +482,9 @@ function AppFrame({
<Box flexDirection="column" width={columns} height={rows}>
<Box flexShrink={0}>
<Header
projectName={projectContext.data?.name ?? AppCopy.placeholderProjectName}
projectName={
projectContext.data?.name ?? AppCopy.placeholderProjectName
}
directoryPath={directoryPath}
version={version}
terminalWidth={columns}
Expand Down Expand Up @@ -535,9 +547,8 @@ function AppFrame({
>
<GoalAuthoringFlow
onComplete={handleGoalAuthoringComplete}
onSuccessAcknowledged={handleGoalAuthoringAcknowledged}
onCancel={handleGoalAuthoringCancel}
dispatchError={goalAuthoringError}
disabled={goalAuthoringWorking}
/>
</Box>
)}
Expand All @@ -564,8 +575,8 @@ function AppFrame({
searchOpen
? []
: cockpitLaunchpadVisible
? COCKPIT_FOOTER_SHORTCUTS
: DEFAULT_FOOTER_SHORTCUTS
? COCKPIT_FOOTER_SHORTCUTS
: DEFAULT_FOOTER_SHORTCUTS
}
notifications={buildNotifications(
daemonStatuses,
Expand All @@ -590,7 +601,8 @@ function buildDaemonFailureNotifications(
.map((status) => ({
id: `daemon-${status.name}-failed`,
title: `${status.name.toUpperCase()} daemon failed`,
body: status.stderr[status.stderr.length - 1] ?? AppCopy.daemonFailureBody,
body:
status.stderr[status.stderr.length - 1] ?? AppCopy.daemonFailureBody,
unread: true,
}));
}
Expand Down
16 changes: 10 additions & 6 deletions src/presentation/tui/goals/AddGoalRequestFactory.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* AddGoalRequestFactory - Assembles an AddGoalRequest from GoalAuthoringValues.
*
* Normalizes free-text authoring fields at the presentation-application
* boundary: blank optional text becomes undefined and whitespace/comma
* separated lists become arrays. Shared by the App overlay and GoalsScreen
* submission paths so the two cannot drift.
* Normalizes optional authoring fields at the presentation-application
* boundary. Scope arrays are copied without parsing so item content and order
* stay intact. Shared by the App overlay and GoalsScreen submission paths so
* the two cannot drift.
*/

import type { AddGoalRequest } from "../../../application/context/goals/add/AddGoalRequest.js";
Expand All @@ -16,8 +16,8 @@ export const AddGoalRequestFactory = {
title: values.title,
objective: values.objective,
successCriteria: [...values.successCriteria],
scopeIn: optionalList(values.scopeIn),
scopeOut: optionalList(values.scopeOut),
scopeIn: optionalArray(values.scopeIn),
scopeOut: optionalArray(values.scopeOut),
nextGoalId: optionalText(values.nextGoal),
previousGoalId: optionalText(values.previousGoal),
prerequisiteGoals: optionalList(values.prerequisiteGoals),
Expand All @@ -40,3 +40,7 @@ function optionalList(value: string): string[] | undefined {

return values.length > 0 ? values : undefined;
}

function optionalArray(values: readonly string[]): string[] | undefined {
return values.length > 0 ? [...values] : undefined;
}
Loading
Loading