Repository: jaiarobotics/jaiabot
Pull request #1171: task/introduce-tasks-to-minimal-jcc/JAIA-1980
Author: login: michael-jaia
Labels: name: task
name: dev-friendly-jcc
Created: 2025-05-08T18:17:09Z | Merged: 2025-05-20T17:57:31Z

### Summary
This pull request brings back the waypoint panel so an operator can click on a waypoint, select a task, and modify the task parameters. The behavior of the task parameters is as follows:
1. Updating the task parameters of one task does not change the task parameters of another task.
2. Updating the task parameters of one task sets the default values for new tasks to be added.
3. When the task parameter values are saved as they are entered and checked against a min and or max.

### Implementation
![task-parameters drawio](https://github.com/user-attachments/assets/11ad793f-f298-405e-a298-00c89c767544)

https://github.com/user-attachments/assets/f5f21749-1871-4251-b64b-000350ece20f

### Testing
1. Selecting a task renders the correct task parameters
2. Changing a task parameter does not change the task parameters of a previous task
3. Changing a task parameter sets the default for a new task

### Note
This is a bigger pull request than normal. It combines some of the sub tasks that were in progress prior to pausing the project.



Diff:
diff --git a/src/web/components/Map/Map.tsx b/src/web/components/Map/Map.tsx
index c3c3444d65..27346376a4 100644
--- a/src/web/components/Map/Map.tsx
+++ b/src/web/components/Map/Map.tsx
@@ -41,6 +41,8 @@ export default function Map() {
                 case MapFeatureTypes.HUB:
                     handleNodeClick(feature);
                     return;
+                case MapFeatureTypes.WAYPOINT:
+                    handleWaypointClick(feature);
                 default:
                     return;
             }
@@ -62,11 +64,21 @@ export default function Map() {
         if (nodeType === NodeTypes.BOT || nodeType == NodeTypes.HUB) {
             jaiaDispatch({
                 type: JaiaActions.CLICKED_NODE,
-                selectedNode: { type: nodeType, id: nodeID },
+                clickedNode: { type: nodeType, id: nodeID },
             });
         }
     };
 
+    const handleWaypointClick = (feature: Feature<Geometry>) => {
+        jaiaDispatch({
+            type: JaiaActions.CLICKED_WAYPOINT,
+            clickedWaypoint: {
+                waypointNum: feature.get("waypointNum"),
+                missionID: feature.get("missionID"),
+            },
+        });
+    };
+
     /**
      * Dispatches action to add a waypoint to the map
      *
diff --git a/src/web/components/Map/__tests__/Map.test.tsx b/src/web/components/Map/__tests__/Map.test.tsx
index 0064dece03..0f99197ec8 100644
--- a/src/web/components/Map/__tests__/Map.test.tsx
+++ b/src/web/components/Map/__tests__/Map.test.tsx
@@ -97,7 +97,7 @@ test("Click on map twice with mission in edit mode", () => {
     let missionLayerFeatures = missionLayer.getVectorLayer().getSource().getFeatures();
     expect(missionLayerFeatures.length).toBe(1);
     expect(missionLayerFeatures[0].get("type")).toBe(MapFeatureTypes.WAYPOINT);
-    expect(missionLayerFeatures[0].get("id")).toBe(1);
+    expect(missionLayerFeatures[0].get("waypointNum")).toBe(1);
 
     act(() => {
         map.dispatchEvent(mapBrowserEventMock);
@@ -107,7 +107,7 @@ test("Click on map twice with mission in edit mode", () => {
     // 2 waypoints and a line segment [waypoint, line, waypoint]
     expect(missionLayerFeatures.length).toBe(3);
     expect(missionLayerFeatures[2].get("type")).toBe(MapFeatureTypes.WAYPOINT);
-    expect(missionLayerFeatures[2].get("id")).toBe(2);
+    expect(missionLayerFeatures[2].get("waypointNum")).toBe(2);
 
     // Reset states
     missions.deleteAllMissions();
@@ -136,7 +136,7 @@ test("Click on map with Bot selected and not assigned to a mission", () => {
     let missionLayerFeatures = missionLayer.getVectorLayer().getSource().getFeatures();
     expect(missionLayerFeatures.length).toBe(1);
     expect(missionLayerFeatures[0].get("type")).toBe(MapFeatureTypes.WAYPOINT);
-    expect(missionLayerFeatures[0].get("id")).toBe(1);
+    expect(missionLayerFeatures[0].get("waypointNum")).toBe(1);
 
     expect(missions.getMissionIDInEditMode()).toBe(1);
 
diff --git a/src/web/components/MissionAssignMenu/MissionAssignMenu.tsx b/src/web/components/MissionAssignMenu/MissionAssignMenu.tsx
index 7c5263226a..00a906fc5c 100644
--- a/src/web/components/MissionAssignMenu/MissionAssignMenu.tsx
+++ b/src/web/components/MissionAssignMenu/MissionAssignMenu.tsx
@@ -67,7 +67,7 @@ export default function MissionAssignMenu(props: Props) {
     };
 
     return (
-        <FormControl sx={{ minWidth: 120 }}>
+        <FormControl sx={{ minWidth: 120 }} size="small">
             <InputLabel>Bot</InputLabel>
             <Select
                 label="Assign"
diff --git a/src/web/components/TaskParameters/TaskParameters.less b/src/web/components/TaskParameters/TaskParameters.less
new file mode 100644
index 0000000000..c897355293
--- /dev/null
+++ b/src/web/components/TaskParameters/TaskParameters.less
@@ -0,0 +1,24 @@
+.task-parameters {
+    display: grid;
+    grid-template-columns: min-content 75px min-content;
+    align-items: center;
+    gap: 12px;
+}
+
+.task-parameters input {
+    all: unset;
+    border: 1px solid #d1d5db;
+    border-radius: 3px;
+    padding: 6px;
+    text-align: right;
+}
+
+.task-parameters .units {
+    // Move units closer to input box
+    margin-left: -6px;
+}
+
+.task-parameters .distance-calc {
+    padding: 6px;
+    text-align: right;
+}
diff --git a/src/web/components/TaskParameters/TaskParameters.tsx b/src/web/components/TaskParameters/TaskParameters.tsx
new file mode 100644
index 0000000000..b5f5f623c4
--- /dev/null
+++ b/src/web/components/TaskParameters/TaskParameters.tsx
@@ -0,0 +1,167 @@
+import React, { useContext } from "react";
+import { JaiaDispatchContext } from "../../context/JaiaContext";
+import { JaiaActions } from "../../context/jaia-actions";
+
+import Task from "../../data/tasks/task";
+
+import { TaskParameterKeys } from "../../types/jaia-system-types";
+import { TaskType } from "../../types/protobuf-types";
+import { formatNumericalInput } from "../../utils/input";
+
+import "./TaskParameters.less";
+
+interface Props {
+    task: Task;
+}
+
+interface SubProps {
+    task: Task;
+    onChange: (evt: React.ChangeEvent<HTMLInputElement>) => void;
+}
+
+/**
+ * Renders input fields for the provided task
+ */
+export default function TaskParameters(props: Props) {
+    const jaiaDispatch = useContext(JaiaDispatchContext);
+
+    const onParameterChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
+        const key = evt.target.name;
+        const value = evt.target.value;
+
+        jaiaDispatch({
+            type: JaiaActions.CHANGE_TASK_PARAMETER,
+            taskParameterPair: { key, value },
+        });
+    };
+
+    switch (props.task?.getType()) {
+        case TaskType.DIVE:
+            return <DiveParameters task={props.task} onChange={onParameterChange} />;
+        case TaskType.SURFACE_DRIFT:
+            return <DriftParameters task={props.task} onChange={onParameterChange} />;
+        case TaskType.CONSTANT_HEADING:
+            return <ConstantHeading task={props.task} onChange={onParameterChange} />;
+        default:
+            return <div></div>;
+    }
+}
+
+/**
+ * Renders input fields for a dive task
+ */
+function DiveParameters(props: SubProps) {
+    const diveParameters = props.task.getDiveParameters();
+
+    return (
+        <div className="task-parameters">
+            <div>Max Depth</div>
+            <input
+                name={TaskParameterKeys.MAX_DEPTH}
+                type="number"
+                value={formatNumericalInput(diveParameters.max_depth)}
+                autoComplete="off"
+                onChange={(evt) => props.onChange(evt)}
+            />
+            <div className="units">m</div>
+
+            <div>Depth Interval</div>
+            <input
+                name={TaskParameterKeys.DEPTH_INTERVAL}
+                type="number"
+                value={formatNumericalInput(diveParameters.depth_interval)}
+                autoComplete="off"
+                onChange={(evt) => props.onChange(evt)}
+            />
+            <div className="units">m</div>
+
+            <div>Hold Time</div>
+            <input
+                name={TaskParameterKeys.HOLD_TIME}
+                type="number"
+                value={formatNumericalInput(diveParameters.hold_time)}
+                autoComplete="off"
+                onChange={(evt) => props.onChange(evt)}
+            />
+            <div className="units">s</div>
+
+            <div>Drift Time</div>
+            <input
+                name={TaskParameterKeys.DRIFT_TIME}
+                type="number"
+                value={formatNumericalInput(props.task.getDrfitParameters().drift_time)}
+                autoComplete="off"
+                onChange={(evt) => props.onChange(evt)}
+            />
+            <div className="units">s</div>
+        </div>
+    );
+}
+
+/**
+ * Renders input fields for a drift task
+ */
+function DriftParameters(props: SubProps) {
+    return (
+        <div className="task-parameters">
+            <div>Drift Time</div>
+            <input
+                name={TaskParameterKeys.DRIFT_TIME}
+                type="number"
+                value={formatNumericalInput(props.task.getDrfitParameters().drift_time)}
+                autoComplete="off"
+                onChange={(evt) => props.onChange(evt)}
+            />
+            <div className="units">s</div>
+        </div>
+    );
+}
+
+/**
+ * Renders input fields for a constant heading task
+ */
+function ConstantHeading(props: SubProps) {
+    const constantHeadingParameters = props.task.getConstantHeadingParameters();
+    return (
+        <div className="task-parameters">
+            <div>Heading</div>
+            <input
+                name={TaskParameterKeys.HEADING}
+                type="number"
+                value={formatNumericalInput(constantHeadingParameters.constant_heading)}
+                autoComplete="off"
+                onChange={(evt) => props.onChange(evt)}
+            />
+            <div className="units">deg</div>
+
+            <div>Time</div>
+            <input
+                name={TaskParameterKeys.CONSTANT_HEADING_TIME}
+                type="number"
+                value={formatNumericalInput(constantHeadingParameters.constant_heading_time)}
+                autoComplete="off"
+                onChange={(evt) => props.onChange(evt)}
+            />
+            <div className="units">s</div>
+
+            <div>Speed</div>
+            <input
+                name={TaskParameterKeys.SPEED}
+                type="number"
+                value={formatNumericalInput(constantHeadingParameters.constant_heading_speed)}
+                autoComplete="off"
+                onChange={(evt) => props.onChange(evt)}
+            />
+            <div className="units">m/s</div>
+
+            <div>Distance</div>
+            <div className="distance-calc">
+                {(
+                    constantHeadingParameters.constant_heading_time *
+                    constantHeadingParameters.constant_heading_speed
+                ).toFixed(0)}
+            </div>
+            <div className="units">m</div>
+        </div>
+    );
+}
diff --git a/src/web/components/TaskParameters/__tests__/TaskParameters.test.tsx b/src/web/components/TaskParameters/__tests__/TaskParameters.test.tsx
new file mode 100644
index 0000000000..63193f44d0
--- /dev/null
+++ b/src/web/components/TaskParameters/__tests__/TaskParameters.test.tsx
@@ -0,0 +1,49 @@
+import { render, screen } from "@testing-library/react";
+import TaskParameters from "../TaskParameters";
+import Task from "../../../data/tasks/task";
+import { TaskType } from "../../../types/protobuf-types";
+import { jaiaGlobal } from "../../../data/jaia_global/jaia-global";
+import { TaskParameterKeys } from "../../../types/jaia-system-types";
+
+const mockTask: Task = new Task();
+
+test("Render dive + drift parameters", () => {
+    mockTask.setType(TaskType.DIVE);
+    render(<TaskParameters task={mockTask} />);
+    const depthParams = screen.getAllByDisplayValue(
+        jaiaGlobal.getDefaultTaskParameters().dive.max_depth,
+    );
+    expect(depthParams[0]).toHaveAttribute("name", TaskParameterKeys.MAX_DEPTH);
+    expect(depthParams[1]).toHaveAttribute("name", TaskParameterKeys.DEPTH_INTERVAL);
+    const timeParams = screen.getAllByDisplayValue(
+        jaiaGlobal.getDefaultTaskParameters().dive.hold_time,
+    );
+    expect(timeParams[0]).toHaveAttribute("name", TaskParameterKeys.HOLD_TIME);
+    expect(timeParams[1]).toHaveAttribute("name", TaskParameterKeys.DRIFT_TIME);
+});
+
+test("Render drift parameters", () => {
+    mockTask.setType(TaskType.SURFACE_DRIFT);
+    render(<TaskParameters task={mockTask} />);
+    const driftParams = screen.getByDisplayValue(
+        jaiaGlobal.getDefaultTaskParameters().drift.drift_time,
+    );
+    expect(driftParams).toHaveAttribute("name", TaskParameterKeys.DRIFT_TIME);
+});
+
+test("Render constant heading parameters", () => {
+    mockTask.setType(TaskType.CONSTANT_HEADING);
+    render(<TaskParameters task={mockTask} />);
+    const heading = screen.getByDisplayValue(
+        jaiaGlobal.getDefaultTaskParameters().constantHeading.constant_heading,
+    );
+    const time = screen.getByDisplayValue(
+        jaiaGlobal.getDefaultTaskParameters().constantHeading.constant_heading_time,
+    );
+    const speed = screen.getByDisplayValue(
+        jaiaGlobal.getDefaultTaskParameters().constantHeading.constant_heading_speed,
+    );
+    expect(heading).toHaveAttribute("name", TaskParameterKeys.HEADING);
+    expect(time).toHaveAttribute("name", TaskParameterKeys.CONSTANT_HEADING_TIME);
+    expect(speed).toHaveAttribute("name", TaskParameterKeys.SPEED);
+});
diff --git a/src/web/components/WaypointPanel/WaypointPanel.less b/src/web/components/WaypointPanel/WaypointPanel.less
new file mode 100644
index 0000000000..d8352194d6
--- /dev/null
+++ b/src/web/components/WaypointPanel/WaypointPanel.less
@@ -0,0 +1,50 @@
+@import "../../style//stylesheets/util.less";
+
+.waypoint-panel-container {
+    position: absolute;
+    top: @panel-top;
+    right: @panel-right;
+
+    padding: 6px;
+
+    max-height: @panel-max-height;
+
+    background-color: @overlay-background;
+}
+
+.waypoint-panel {
+    display: grid;
+    grid-template-columns: min-content min-content;
+    align-items: center;
+    gap: 12px;
+
+    padding: 12px;
+
+    background-color: @cc-background-color;
+    font-size: 1.1rem;
+}
+
+.waypoint-panel .label {
+    font-weight: 600;
+}
+
+.waypoint-input-container {
+    display: grid;
+    grid-template-columns: min-content 1fr;
+    column-gap: 12px;
+    align-items: center;
+}
+
+.jaia-button.delete-waypoint {
+    justify-self: center;
+}
+
+.waypoint-panel .line-break {
+    grid-column: 1 / 3;
+    height: 1px;
+    background-color: #d1d5db;
+}
+
+.waypoint-panel .task-parameters-container {
+    grid-column: 2 / 3;
+}
diff --git a/src/web/components/WaypointPanel/WaypointPanel.tsx b/src/web/components/WaypointPanel/WaypointPanel.tsx
new file mode 100644
index 0000000000..f0a42be6fd
--- /dev/null
+++ b/src/web/components/WaypointPanel/WaypointPanel.tsx
@@ -0,0 +1,137 @@
+import { useContext, useEffect } from "react";
+
+import TaskParameters from "../TaskParameters/TaskParameters";
+import { JaiaContext, JaiaDispatchContext } from "../../context/JaiaContext";
+import { JaiaActions } from "../../context/jaia-actions";
+import { missionsManager } from "../../data/missions_manager/missions-manager";
+import { TaskType } from "../../types/protobuf-types";
+
+import { UNASSIGNED_ID, LAT_LON_DECIMALS } from "../../utils/constants";
+
+import Icon from "@mdi/react";
+import { mdiDelete } from "@mdi/js";
+import { Button, FormControl, Select, MenuItem, SelectChangeEvent } from "@mui/material";
+
+import "./WaypointPanel.less";
+
+export default function WaypointPanel() {
+    const jaiaContext = useContext(JaiaContext);
+    const jaiaDispatchContext = useContext(JaiaDispatchContext);
+
+    useEffect(() => {
+        return () => {
+            jaiaDispatchContext({ type: JaiaActions.CLOSED_WAYPOINT_PANEL });
+        };
+    }, []);
+
+    const getWaypoint = () => {
+        const mission = jaiaContext.missions.get(jaiaContext.selectedWaypoint.missionID);
+        return mission.getWaypoint(jaiaContext.selectedWaypoint.waypointNum);
+    };
+
+    const getTaskType = () => {
+        const taskType = getWaypoint().getTask()?.getType();
+
+        if (!taskType) {
+            return TaskType.NONE;
+        }
+
+        return taskType;
+    };
+
+    const formatBotID = () => {
+        const botID = missionsManager.getBotID(jaiaContext.selectedWaypoint.missionID);
+
+        if (botID === UNASSIGNED_ID) {
+            return "";
+        }
+
+        return botID;
+    };
+
+    const formatMenuItemText = (taskType: TaskType) => {
+        switch (taskType) {
+            case TaskType.NONE:
+                return "None";
+            case TaskType.DIVE:
+                return "Dive";
+            case TaskType.SURFACE_DRIFT:
+                return "Surface Drift";
+            case TaskType.STATION_KEEP:
+                return "Station Keep";
+            case TaskType.CONSTANT_HEADING:
+                return "Constant Heading";
+            default:
+                return "";
+        }
+    };
+
+    const handleDeleteWaypointClick = () => {
+        jaiaDispatchContext({ type: JaiaActions.DELETE_WAYPOINT });
+    };
+
+    const handleTaskMenuSelection = (evt: SelectChangeEvent) => {
+        const selectedTaskType = evt.target.value;
+        jaiaDispatchContext({ type: JaiaActions.SELECT_TASK, taskType: selectedTaskType });
+    };
+
+    return (
+        <div className="waypoint-panel-container">
+            <div className="waypoint-panel">
+                <div className="label">Wpt:</div>
+                <div className="waypoint-input-container">
+                    <div>{jaiaContext.selectedWaypoint.waypointNum}</div>
+                    <Button
+                        className="jaia-button delete-waypoint"
+                        onClick={() => handleDeleteWaypointClick()}
+                    >
+                        <Icon path={mdiDelete} title="Delete Waypoint" />
+                    </Button>
+                </div>
+
+                <div className="line-break"></div>
+
+                <div className="label">Bot:</div>
+                <div>{formatBotID()}</div>
+
+                <div className="line-break"></div>
+
+                <div className="label">Lat:</div>
+                <div>{getWaypoint().getLocation().lat.toFixed(LAT_LON_DECIMALS)}</div>
+
+                <div className="label">Lon:</div>
+                <div>{getWaypoint().getLocation().lon.toFixed(LAT_LON_DECIMALS)}</div>
+
+                <div className="line-break"></div>
+
+                <div className="label">Task:</div>
+                <FormControl sx={{ minWidth: 120 }} size="small">
+                    <Select
+                        value={getTaskType()}
+                        onChange={(evt: SelectChangeEvent) => handleTaskMenuSelection(evt)}
+                    >
+                        <MenuItem value={TaskType.NONE}>
+                            {formatMenuItemText(TaskType.NONE)}
+                        </MenuItem>
+                        <MenuItem value={TaskType.DIVE}>
+                            {formatMenuItemText(TaskType.DIVE)}
+                        </MenuItem>
+                        <MenuItem value={TaskType.SURFACE_DRIFT}>
+                            {formatMenuItemText(TaskType.SURFACE_DRIFT)}
+                        </MenuItem>
+                        <MenuItem value={TaskType.CONSTANT_HEADING}>
+                            {formatMenuItemText(TaskType.CONSTANT_HEADING)}
+                        </MenuItem>
+                        <MenuItem value={TaskType.STATION_KEEP}>
+                            {formatMenuItemText(TaskType.STATION_KEEP)}
+                        </MenuItem>
+                    </Select>
+                </FormControl>
+
+                <div className="task-parameters-container">
+                    <TaskParameters task={getWaypoint().getTask()} />
+                </div>
+            </div>
+        </div>
+    );
+}
diff --git a/src/web/containers/BotDetails/BotDetails.tsx b/src/web/containers/BotDetails/BotDetails.tsx
index 1b9bfe8184..620990f29e 100644
--- a/src/web/containers/BotDetails/BotDetails.tsx
+++ b/src/web/containers/BotDetails/BotDetails.tsx
@@ -9,7 +9,6 @@ import {
     JaiaAction,
 } from "../../context/JaiaContext";
 import { JaiaActions } from "../../context/jaia-actions";
-import { NodeTypes } from "../../types/jaia-system-types";
 import { DETAILS_DECIMALS, UNASSIGNED_ID } from "../../utils/constants";
 import { BotAccordionNames } from "../../types/context-types";
 
@@ -68,10 +67,6 @@ export default function BotDetails() {
         }),
     );
 
-    if (jaiaContext === null || jaiaContext.visibleDetails !== NodeTypes.BOT) {
-        return <div></div>;
-    }
-
     const hub = jaiaContext.hubs.get(DEFAULT_HUB_ID);
 
     const botID = jaiaContext.selectedNode.id;
diff --git a/src/web/containers/HubDetails/HubDetails.tsx b/src/web/containers/HubDetails/HubDetails.tsx
index bf624dd749..acb3333f8e 100644
--- a/src/web/containers/HubDetails/HubDetails.tsx
+++ b/src/web/containers/HubDetails/HubDetails.tsx
@@ -53,10 +53,6 @@ export default function HubDetails() {
         addDropdownListener("accordionContainer", "hubDetailsAccordionContainer", 30);
     }, []);
 
-    if (jaiaContext === null || jaiaContext.visibleDetails !== NodeTypes.HUB) {
-        return <div></div>;
-    }
-
     const hub = jaiaContext.hubs.get(DEFAULT_HUB_ID);
 
     if (!hub) {
diff --git a/src/web/containers/NodeList/NodeList.tsx b/src/web/containers/NodeList/NodeList.tsx
index 8730c25319..090d21e33b 100644
--- a/src/web/containers/NodeList/NodeList.tsx
+++ b/src/web/containers/NodeList/NodeList.tsx
@@ -36,7 +36,7 @@ export default function NodeList() {
     const handleClick = (nodeType: NodeTypes, nodeID: number) => {
         JaiaDispatch({
             type: JaiaActions.CLICKED_NODE,
-            selectedNode: { type: nodeType, id: nodeID },
+            clickedNode: { type: nodeType, id: nodeID },
         });
     };
 
diff --git a/src/web/context/JaiaContext.tsx b/src/web/context/JaiaContext.tsx
index 92dacd4f3a..ca80cc28c8 100644
--- a/src/web/context/JaiaContext.tsx
+++ b/src/web/context/JaiaContext.tsx
@@ -10,14 +10,20 @@ import { missionsManager } from "../data/missions_manager/missions-manager";
 import Bot from "../data/bots/bot";
 import Hub from "../data/hubs/hub";
 import Mission from "../data/missions/mission";
+import Task from "../data/tasks/task";
 
 import { botLayer } from "../openlayers/layers/vector/bot-layer";
 import { hubLayer } from "../openlayers/layers/vector/hub-layer";
 import { missionLayer } from "../openlayers/layers/vector/mission-layer";
 
-import { GeographicCoordinate } from "../types/protobuf-types";
-import { NodeTypes, SelectedNode } from "../types/jaia-system-types";
+import { GeographicCoordinate, TaskType } from "../types/protobuf-types";
 import { DATA_MODEL_POLL_TIME, UNASSIGNED_ID } from "../utils/constants";
+import {
+    NodeTypes,
+    SelectedNode,
+    SelectedWaypoint,
+    TaskParameterPair,
+} from "../types/jaia-system-types";
 import {
     HubAccordionStates,
     BotAccordionStates,
@@ -34,6 +40,7 @@ export interface JaiaContextType {
     missions: Map<number, Mission>;
 
     selectedNode: SelectedNode;
+    selectedWaypoint: SelectedWaypoint;
     visibleDetails: NodeTypes;
     visiblePanel: PanelNames;
     hubAccordionStates: HubAccordionStates;
@@ -48,8 +55,12 @@ export interface JaiaAction {
     botID?: number;
     missionID?: number;
 
-    selectedNode?: SelectedNode;
+    clickedNode?: SelectedNode;
+    clickedWaypoint?: SelectedWaypoint;
+
     location?: GeographicCoordinate;
+    taskType?: TaskType;
+    taskParameterPair?: TaskParameterPair;
 
     hubAccordionName?: HubAccordionNames;
     botAccordionName?: BotAccordionNames;
@@ -62,13 +73,13 @@ interface JaiaContextProviderProps {
     children: ReactNode;
 }
 
-const defaultHubAccordionStates = {
+const defaultHubAccordionStates: HubAccordionStates = {
     quickLook: false,
     commands: false,
     links: false,
 };
 
-const defaultBotAccordionStates = {
+const defaultBotAccordionStates: BotAccordionStates = {
     quickLook: false,
     commands: false,
     advancedCommands: false,
@@ -123,11 +134,23 @@ function jaiaReducer(state: JaiaContextType, action: JaiaAction) {
         case JaiaActions.ADD_WAYPOINT:
             return handleAddWaypoint(mutableState, action.location);
 
+        case JaiaActions.DELETE_WAYPOINT:
+            return handleDeleteWaypoint(mutableState);
+
+        case JaiaActions.SELECT_TASK:
+            return handleSelectTask(mutableState, action.taskType);
+
+        case JaiaActions.CHANGE_TASK_PARAMETER:
+            return handleChangeTaskParameter(mutableState, action.taskParameterPair);
+
         case JaiaActions.CLOSED_DETAILS:
             return handleClosedDetails(mutableState);
 
+        case JaiaActions.CLOSED_WAYPOINT_PANEL:
+            return handleClosedWaypointPanel(mutableState);
+
         case JaiaActions.CLICKED_NODE:
-            return handleClickedNode(mutableState, action.selectedNode);
+            return handleClickedNode(mutableState, action.clickedNode);
 
         case JaiaActions.CLICKED_HUB_ACCORDION:
             return handleClickedHubAccordion(mutableState, action.hubAccordionName);
@@ -151,6 +174,9 @@ function jaiaReducer(state: JaiaContextType, action: JaiaAction) {
         case JaiaActions.CLICKED_PANEL_BUTTON:
             return handleClickedPanelButton(mutableState, action.panelName);
 
+        case JaiaActions.CLICKED_WAYPOINT:
+            return handleClickedWaypoint(mutableState, action.clickedWaypoint);
+
         default:
             return state;
     }
@@ -324,6 +350,66 @@ function handleAddWaypoint(mutableState: JaiaContextType, location: GeographicCo
     return mutableState;
 }
 
+/**
+ * Makes call to remove a waypoint from a mission
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleDeleteWaypoint(mutableState: JaiaContextType) {
+    const mission = missions.getMission(jaiaGlobal.getSelectedWaypoint().missionID);
+    mission.deleteWaypoint(jaiaGlobal.getSelectedWaypoint().waypointNum);
+    jaiaGlobal.setSelectedWaypoint({ waypointNum: UNASSIGNED_ID, missionID: UNASSIGNED_ID });
+
+    mutableState.missions = missions.getMissions();
+    mutableState.selectedWaypoint = jaiaGlobal.getSelectedWaypoint();
+    mutableState.visiblePanel = PanelNames.NONE;
+
+    missionLayer.updateFeatures();
+
+    return mutableState;
+}
+
+/**
+ * Updates the task associated with a waypoint based on the operator's selection
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @param {TaskType} taskType Name of the task selected
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleSelectTask(mutableState: JaiaContextType, taskType: TaskType) {
+    const waypoint = getWaypoint();
+    const task = waypoint.getTask();
+
+    if (task) {
+        task.setType(taskType);
+    } else {
+        const newTask = new Task();
+        newTask.setType(taskType);
+        waypoint.setTask(newTask);
+    }
+
+    missionLayer.updateFeatures();
+
+    return mutableState;
+}
+
+/**
+ * Makes call to update the parameters of a task based on user input
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @param {TaskParameterPair} taskParameterPair The name of the input updated and its value
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleChangeTaskParameter(
+    mutableState: JaiaContextType,
+    taskParameterPair: TaskParameterPair,
+) {
+    const task = getWaypoint().getTask();
+    task.setParameter(taskParameterPair);
+    return mutableState;
+}
+
 /**
  * Closes the Bot or Hub details panel
  *
@@ -335,6 +421,20 @@ function handleClosedDetails(mutableState: JaiaContextType) {
     return mutableState;
 }
 
+/**
+ * Handles cleanup when a waypoint panel closes
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleClosedWaypointPanel(mutableState: JaiaContextType) {
+    jaiaGlobal.setSelectedWaypoint({ waypointNum: UNASSIGNED_ID, missionID: UNASSIGNED_ID });
+
+    mutableState.selectedWaypoint = jaiaGlobal.getSelectedWaypoint();
+
+    return mutableState;
+}
+
 /**
  * Handles click events for the Bot and Hub icons on the map and in the NodeList component
  *
@@ -515,6 +615,22 @@ function handleClickedPanelButton(mutableState: JaiaContextType, panelName: Pane
     return mutableState;
 }
 
+/**
+ * Opens panel for the selected waypoint
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @param {SelectedWaypoint} clickedWaypoint Identifies which waypoint was clicked by operator
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleClickedWaypoint(mutableState: JaiaContextType, clickedWaypoint: SelectedWaypoint) {
+    jaiaGlobal.setSelectedWaypoint(clickedWaypoint);
+
+    mutableState.selectedWaypoint = jaiaGlobal.getSelectedWaypoint();
+    mutableState.visiblePanel = PanelNames.WAYPOINT;
+
+    return mutableState;
+}
+
 export function JaiaContextProvider({ children }: JaiaContextProviderProps) {
     const [state, dispatch] = useReducer(jaiaReducer, null);
 
@@ -563,3 +679,14 @@ function syncOpenLayers() {
     hubLayer.updateFeatures();
     missionLayer.updateFeatures();
 }
+
+/**
+ * Retrieves the Waypoint object that is currently selected
+ *
+ * @returns {Waypoint} Access to Waypoint modifiers
+ */
+function getWaypoint() {
+    const selectedWaypoint = jaiaGlobal.getSelectedWaypoint();
+    const mission = missions.getMission(selectedWaypoint.missionID);
+    return mission.getWaypoint(selectedWaypoint.waypointNum);
+}
diff --git a/src/web/context/jaia-actions.ts b/src/web/context/jaia-actions.ts
index 1ff8bc1aca..a33b3bac39 100644
--- a/src/web/context/jaia-actions.ts
+++ b/src/web/context/jaia-actions.ts
@@ -7,8 +7,12 @@ export enum JaiaActions {
     ASSIGN_MISSION = "ASSIGN_MISSION",
     AUTO_ASSIGN_MISSIONS = "AUTO_ASSIGN_MISSIONS",
     ADD_WAYPOINT = "ADD_WAYPOINT",
+    DELETE_WAYPOINT = "DELETE_WAYPOINT",
+    SELECT_TASK = "SELECT_TASK",
+    CHANGE_TASK_PARAMETER = "CHANGE_TASK_PARAMETER",
 
     CLOSED_DETAILS = "CLOSED_DETAILS",
+    CLOSED_WAYPOINT_PANEL = "CLOSED_WAYPOINT_PANEL",
     CLICKED_NODE = "CLICKED_NODE",
     CLICKED_HUB_ACCORDION = "CLICKED_HUB_ACCORDION",
     CLICKED_BOT_ACCORDION = "CLICKED_BOT_ACCORDION",
@@ -16,4 +20,5 @@ export enum JaiaActions {
     CLICKED_MISSION_ACCORDION = "CLICKED_MISSION_ACCORDION",
     CLICKED_EDIT_MISSION = "CLICKED_EDIT_MISSION",
     CLICKED_PANEL_BUTTON = "CLICKED_PANEL_BUTTON",
+    CLICKED_WAYPOINT = "CLICKED_WAYPOINT",
 }
diff --git a/src/web/data/jaia_global/jaia-global.ts b/src/web/data/jaia_global/jaia-global.ts
index 266fb2a62a..8599e76794 100644
--- a/src/web/data/jaia_global/jaia-global.ts
+++ b/src/web/data/jaia_global/jaia-global.ts
@@ -1,27 +1,68 @@
-import { NodeTypes, SelectedNode } from "../../types/jaia-system-types";
+import {
+    NodeTypes,
+    SelectedNode,
+    SelectedWaypoint,
+    TaskParameters,
+} from "../../types/jaia-system-types";
 import { UNASSIGNED_ID } from "../../utils/constants";
 
+const defaultTaskParameters: TaskParameters = {
+    dive: {
+        max_depth: 10,
+        depth_interval: 10,
+        hold_time: 0,
+    },
+    drift: {
+        drift_time: 0,
+    },
+    constantHeading: {
+        constant_heading: 180,
+        constant_heading_time: 30,
+        constant_heading_speed: 2,
+    },
+};
+
 class JaiaGlobal {
     private selectedNode: SelectedNode;
+    private selectedWaypoint: SelectedWaypoint;
+    private defaultTaskParameters: TaskParameters;
 
     constructor() {
         this.selectedNode = { type: NodeTypes.NONE, id: UNASSIGNED_ID };
+        this.selectedWaypoint = { waypointNum: UNASSIGNED_ID, missionID: UNASSIGNED_ID };
+        this.defaultTaskParameters = defaultTaskParameters;
     }
 
     getSelectedNode() {
         return this.selectedNode;
     }
 
-    setSelectedNode(selectedNode: SelectedNode) {
+    setSelectedNode(clickedNode: SelectedNode) {
         if (
-            selectedNode.type === this.getSelectedNode().type &&
-            selectedNode.id === this.getSelectedNode().id
+            clickedNode.type === this.getSelectedNode().type &&
+            clickedNode.id === this.getSelectedNode().id
         ) {
             this.selectedNode = { type: NodeTypes.NONE, id: UNASSIGNED_ID };
         } else {
-            this.selectedNode = selectedNode;
+            this.selectedNode = clickedNode;
         }
     }
+
+    getSelectedWaypoint() {
+        return this.selectedWaypoint;
+    }
+
+    setSelectedWaypoint(clickedWaypoint: SelectedWaypoint) {
+        this.selectedWaypoint = clickedWaypoint;
+    }
+
+    getDefaultTaskParameters() {
+        return this.defaultTaskParameters;
+    }
+
+    setDefaultTaskParameters(defaultTaskParameters: TaskParameters) {
+        this.defaultTaskParameters = defaultTaskParameters;
+    }
 }
 
 export const jaiaGlobal = new JaiaGlobal();
diff --git a/src/web/data/tasks/parameters/constant-heading-parameters.ts b/src/web/data/tasks/parameters/constant-heading-parameters.ts
deleted file mode 100644
index c9818c71d4..0000000000
--- a/src/web/data/tasks/parameters/constant-heading-parameters.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-export default class ConstantHeadingParameters {
-    private heading: number;
-    private time: number;
-    private speed: number;
-
-    constructor() {}
-
-    getHeading() {
-        return this.heading;
-    }
-
-    setHeading(heading: number) {
-        this.heading = heading;
-    }
-
-    getTime() {
-        return this.time;
-    }
-
-    setTime(time: number) {
-        this.time = time;
-    }
-
-    getSpeed() {
-        return this.speed;
-    }
-
-    setSpeed(speed: number) {
-        this.speed = speed;
-    }
-}
diff --git a/src/web/data/tasks/parameters/dive-parameters.ts b/src/web/data/tasks/parameters/dive-parameters.ts
deleted file mode 100644
index 03a7ccd8cd..0000000000
--- a/src/web/data/tasks/parameters/dive-parameters.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-export default class DiveParameters {
-    private maxDepth: number;
-    private depthInterval: number;
-    private holdTime: number;
-    private isBottomDive: boolean;
-
-    constructor() {}
-
-    getMaxDepth() {
-        return this.maxDepth;
-    }
-
-    setMaxDepth(maxDepth: number) {
-        this.maxDepth = maxDepth;
-    }
-
-    getDepthInterval() {
-        return this.depthInterval;
-    }
-
-    setDepthInterval(depthInterval: number) {
-        this.depthInterval = depthInterval;
-    }
-
-    getHoldTime() {
-        return this.holdTime;
-    }
-
-    setHoldTime(holdTime: number) {
-        this.holdTime = holdTime;
-    }
-
-    getIsBottomDive() {
-        return this.isBottomDive;
-    }
-
-    setIsBottomDive(isBottomDive: boolean) {
-        this.isBottomDive = isBottomDive;
-    }
-}
diff --git a/src/web/data/tasks/parameters/drift-parameters.ts b/src/web/data/tasks/parameters/drift-parameters.ts
deleted file mode 100644
index f0680c7e77..0000000000
--- a/src/web/data/tasks/parameters/drift-parameters.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export default class DriftParameters {
-    private time: number;
-
-    constructor() {}
-
-    getTime() {
-        return this.time;
-    }
-
-    setTime(time: number) {
-        this.time = time;
-    }
-}
diff --git a/src/web/data/tasks/parameters/station-keep-parameters.ts b/src/web/data/tasks/parameters/station-keep-parameters.ts
deleted file mode 100644
index 3cb32c4bbd..0000000000
--- a/src/web/data/tasks/parameters/station-keep-parameters.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export default class StationKeepParameters {
-    private time: number;
-
-    constructor() {}
-
-    getTime() {
-        return this.time;
-    }
-
-    setTime(time: number) {
-        this.time = time;
-    }
-}
diff --git a/src/web/data/tasks/task.ts b/src/web/data/tasks/task.ts
index 3fe6045406..e22366d237 100644
--- a/src/web/data/tasks/task.ts
+++ b/src/web/data/tasks/task.ts
@@ -1,41 +1,183 @@
-import { TaskType } from "../../types/protobuf-types";
-import ConstantHeadingParameters from "./parameters/constant-heading-parameters";
-import DiveParameters from "./parameters/dive-parameters";
-import DriftParameters from "./parameters/drift-parameters";
-import StationKeepParameters from "./parameters/station-keep-parameters";
+import { jaiaGlobal } from "../jaia_global/jaia-global";
+import { TaskParameterKeys, TaskParameterPair } from "../../types/jaia-system-types";
+import {
+    ConstantHeadingParameters,
+    DiveParameters,
+    DriftParameters,
+    TaskType,
+} from "../../types/protobuf-types";
 
 export default class Task {
     private type: TaskType;
+
+    // Parameters //
     private diveParameters: DiveParameters;
     private driftParameters: DriftParameters;
     private constantHeadingParameters: ConstantHeadingParameters;
-    private stationKeepParameters: StationKeepParameters;
+
+    // Parameter Constraints //
+    NO_CONSTRAINT = -1;
+
+    // Dive
+    MIN_DEPTH_CONSTRAINT = 0;
+    MAX_DEPTH_CONSTRAINT = 50;
+    MIN_HOLD_TIME_CONSTRAINT = 0;
+    MAX_HOLD_TIME_CONSTRAINT = 120;
+
+    // Drift
+    MIN_DRIFT_TIME_CONSTRAINT = 0;
+
+    // Constant Heading
+    MIN_HEADING_CONSTRAINT = 0;
+    MAX_HEADING_CONSTRAINT = 360;
+    MIN_CONSTANT_HEADING_TIME_CONSTRAINT = 0;
+    MIN_SPEED_CONSTRAINT = 1;
+    MAX_SPEED_CONSTRAINT = 3;
+
     private isEnablePAM: boolean;
 
-    constructor() {}
+    constructor() {
+        const defaults = jaiaGlobal.getDefaultTaskParameters();
+        this.setDiveParameters(defaults.dive);
+        this.setDriftParameters(defaults.drift);
+        this.setConstantHeadingParameters(defaults.constantHeading);
+    }
 
     getType() {
         return this.type;
     }
 
+    /**
+     * Initializes the task parameters when an operator selects a new task
+     *
+     * @param {TaskType} type Name of selected task
+     * @returns {void}
+     */
     setType(type: TaskType) {
+        const defaults = jaiaGlobal.getDefaultTaskParameters();
+
+        switch (type) {
+            case TaskType.DIVE:
+                this.setDiveParameters(defaults.dive);
+                this.setDriftParameters(defaults.drift);
+                break;
+            case TaskType.SURFACE_DRIFT:
+                this.setDriftParameters(defaults.drift);
+                break;
+            case TaskType.CONSTANT_HEADING:
+                this.setConstantHeadingParameters(defaults.constantHeading);
+                break;
+        }
+
         this.type = type;
     }
 
+    /**
+     * Handles inputs made by the operator to change task parameters
+     *
+     * @param {TaskParameterPair} taskParameterPair Name of parameter + its value
+     * @returns {void}
+     */
+    setParameter(taskParameterPair: TaskParameterPair) {
+        const key = taskParameterPair.key;
+        let value = taskParameterPair.value;
+
+        switch (key) {
+            case TaskParameterKeys.MAX_DEPTH:
+                value = this.validateInput(
+                    value,
+                    this.MIN_DEPTH_CONSTRAINT,
+                    this.MAX_DEPTH_CONSTRAINT,
+                );
+                this.diveParameters.max_depth = value;
+                break;
+            case TaskParameterKeys.DEPTH_INTERVAL:
+                value = this.validateInput(
+                    value,
+                    this.MIN_DEPTH_CONSTRAINT,
+                    this.MAX_DEPTH_CONSTRAINT,
+                );
+                this.diveParameters.depth_interval = value;
+                break;
+            case TaskParameterKeys.HOLD_TIME:
+                value = this.validateInput(
+                    value,
+                    this.MIN_HOLD_TIME_CONSTRAINT,
+                    this.MAX_HOLD_TIME_CONSTRAINT,
+                );
+                this.diveParameters.hold_time = value;
+                break;
+            case TaskParameterKeys.DRIFT_TIME:
+                value = this.validateInput(
+                    value,
+                    this.MIN_DRIFT_TIME_CONSTRAINT,
+                    this.NO_CONSTRAINT,
+                );
+                this.driftParameters.drift_time = value;
+                break;
+            case TaskParameterKeys.HEADING:
+                value = this.validateInput(
+                    value,
+                    this.MIN_HEADING_CONSTRAINT,
+                    this.MAX_HEADING_CONSTRAINT,
+                );
+                this.constantHeadingParameters.constant_heading = value;
+                break;
+            case TaskParameterKeys.CONSTANT_HEADING_TIME:
+                value = this.validateInput(
+                    value,
+                    this.MIN_CONSTANT_HEADING_TIME_CONSTRAINT,
+                    this.NO_CONSTRAINT,
+                );
+                this.constantHeadingParameters.constant_heading_time = value;
+                break;
+            case TaskParameterKeys.SPEED:
+                value = this.validateInput(
+                    value,
+                    this.MIN_SPEED_CONSTRAINT,
+                    this.MAX_SPEED_CONSTRAINT,
+                );
+                this.constantHeadingParameters.constant_heading_speed = value;
+                break;
+        }
+
+        this.updateDefaultTaskParameters();
+    }
+
+    private validateInput(value: number, min: number, max: number) {
+        if (value > max && max !== this.NO_CONSTRAINT) {
+            return max;
+        }
+
+        if (value < min && min !== this.NO_CONSTRAINT) {
+            return min;
+        }
+
+        return value;
+    }
+
+    private updateDefaultTaskParameters() {
+        jaiaGlobal.setDefaultTaskParameters({
+            dive: this.getDiveParameters(),
+            drift: this.getDrfitParameters(),
+            constantHeading: this.getConstantHeadingParameters(),
+        });
+    }
+
     getDiveParameters() {
         return this.diveParameters;
     }
 
     setDiveParameters(diveParameters: DiveParameters) {
-        this.diveParameters = diveParameters;
+        this.diveParameters = { ...diveParameters };
     }
 
-    getDriftParameters() {
+    getDrfitParameters() {
         return this.driftParameters;
     }
 
     setDriftParameters(driftParameters: DriftParameters) {
-        this.driftParameters = driftParameters;
+        this.driftParameters = { ...driftParameters };
     }
 
     getConstantHeadingParameters() {
@@ -43,15 +185,7 @@ export default class Task {
     }
 
     setConstantHeadingParameters(constantHeadingParameters: ConstantHeadingParameters) {
-        this.constantHeadingParameters = constantHeadingParameters;
-    }
-
-    getStationKeepParameters() {
-        return this.stationKeepParameters;
-    }
-
-    setStationKeepParameters(stationKeepParameters: StationKeepParameters) {
-        this.stationKeepParameters = stationKeepParameters;
+        this.constantHeadingParameters = { ...constantHeadingParameters };
     }
 
     getIsEnablePAM() {
diff --git a/src/web/data/tests/__mocks__/task-mock.ts b/src/web/data/tests/__mocks__/task-mock.ts
deleted file mode 100644
index 5979d5ab1b..0000000000
--- a/src/web/data/tests/__mocks__/task-mock.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import Task from "../../tasks/task";
-import DiveParameters from "../../tasks/parameters/dive-parameters";
-import DriftParameters from "../../tasks/parameters/drift-parameters";
-import StationKeepParameters from "../../tasks/parameters/station-keep-parameters";
-import ConstantHeadingParameters from "../../tasks/parameters/constant-heading-parameters";
-
-const diveParameters = new DiveParameters();
-diveParameters.setMaxDepth(10);
-diveParameters.setDepthInterval(10);
-diveParameters.setHoldTime(60);
-
-const bottomDiveParameters = new DiveParameters();
-bottomDiveParameters.setIsBottomDive(true);
-
-const driftParameters = new DriftParameters();
-driftParameters.setTime(60);
-
-const stationKeepParameters = new StationKeepParameters();
-stationKeepParameters.setTime(60);
-
-const constantHeadingParameters = new ConstantHeadingParameters();
-constantHeadingParameters.setHeading(120);
-constantHeadingParameters.setTime(60);
-constantHeadingParameters.setSpeed(3);
-
-const diveTask = new Task();
-diveTask.setDiveParameters(diveParameters);
-
-const bottomDiveTask = new Task();
-bottomDiveTask.setDiveParameters(bottomDiveParameters);
-
-const driftTask = new Task();
-driftTask.setDriftParameters(driftParameters);
-
-const diveDriftTask = new Task();
-diveDriftTask.setDiveParameters(diveParameters);
-diveDriftTask.setDriftParameters(driftParameters);
-
-const stationKeepTask = new Task();
-stationKeepTask.setStationKeepParameters(stationKeepParameters);
-
-const constantHeadingTask = new Task();
-constantHeadingTask.setConstantHeadingParameters(constantHeadingParameters);
-
-export const tasks: Task[] = [
-    diveTask,
-    bottomDiveTask,
-    driftTask,
-    diveDriftTask,
-    stationKeepTask,
-    constantHeadingTask,
-];
diff --git a/src/web/jcc/App.tsx b/src/web/jcc/App.tsx
index 55b42de6eb..679f9c8c13 100644
--- a/src/web/jcc/App.tsx
+++ b/src/web/jcc/App.tsx
@@ -1,8 +1,10 @@
+import { useContext } from "react";
+
 // Jaia
-import { JaiaContextProvider } from "../context/JaiaContext";
+import { JaiaContext, JaiaContextProvider } from "../context/JaiaContext";
+import { PanelNames } from "../types/context-types";
+import { NodeTypes } from "../types/jaia-system-types";
 
-// Style
-import "./App.less";
 import Map from "../components/Map/Map";
 import NodeList from "../containers/NodeList/NodeList";
 import TopButtonList from "../components/TopButtonList/TopButtonList";
@@ -10,6 +12,10 @@ import SideButtonList from "../components/SideButtonList/SideButtonList";
 import HubDetails from "../containers/HubDetails/HubDetails";
 import BotDetails from "../containers/BotDetails/BotDetails";
 import MissionsPanel from "../containers/MissionsPanel/MissionsPanel";
+import WaypointPanel from "../components/WaypointPanel/WaypointPanel";
+
+// Style
+import "./App.less";
 
 /**
  * The root of the JCC interface
@@ -22,10 +28,49 @@ export default function App() {
                 <NodeList />
                 <TopButtonList />
                 <SideButtonList />
-                <HubDetails />
-                <BotDetails />
-                <MissionsPanel />
+                <Details />
+                <Panel />
             </JaiaContextProvider>
         </div>
     );
 }
+
+/**
+ * Controls the rendering of HubDetails and BotDetails
+ */
+function Details() {
+    const jaiaContext = useContext(JaiaContext);
+
+    if (jaiaContext === null) {
+        return <div></div>;
+    }
+
+    switch (jaiaContext.visibleDetails) {
+        case NodeTypes.HUB:
+            return <HubDetails />;
+        case NodeTypes.BOT:
+            return <BotDetails />;
+        default:
+            return <div></div>;
+    }
+}
+
+/**
+ *  Controls the rendering of JCC panels
+ */
+function Panel() {
+    const jaiaContext = useContext(JaiaContext);
+
+    if (jaiaContext === null) {
+        return <div></div>;
+    }
+
+    switch (jaiaContext.visiblePanel) {
+        case PanelNames.MISSIONS:
+            return <MissionsPanel />;
+        case PanelNames.WAYPOINT:
+            return <WaypointPanel />;
+        default:
+            return <div></div>;
+    }
+}
diff --git a/src/web/openlayers/features/waypoint-feature.ts b/src/web/openlayers/features/waypoint-feature.ts
index 0ae836733f..42afc0cf23 100644
--- a/src/web/openlayers/features/waypoint-feature.ts
+++ b/src/web/openlayers/features/waypoint-feature.ts
@@ -42,7 +42,8 @@ export function generateWaypointFeature(
     });
 
     feature.set("type", MapFeatureTypes.WAYPOINT);
-    feature.set("id", waypointNum);
+    feature.set("waypointNum", waypointNum);
+    feature.set("missionID", missionID);
     feature.setStyle(generateWaypointStyle(waypointNum, missionID));
     return feature;
 }
diff --git a/src/web/style/stylesheets/details.less b/src/web/style/stylesheets/details.less
index ac6d59cbcc..7e1a248e11 100644
--- a/src/web/style/stylesheets/details.less
+++ b/src/web/style/stylesheets/details.less
@@ -6,7 +6,9 @@
 
     padding: 6px 12px 12px 12px;
 
-    width: 315px;
+    width: 25vw;
+    min-width: 300px;
+    max-width: 315px;
     max-height: @panel-max-height;
 
     display: flex;
@@ -66,11 +68,15 @@
 
     table {
         margin: 8px 0;
+        font-size: 1rem;
     }
 
     td {
-        padding: 6px 12px;
-        font-size: 1rem;
+        padding: 6px 0;
+    }
+
+    td:first-child {
+        padding-right: 6px;
     }
 }
 
diff --git a/src/web/types/context-types.ts b/src/web/types/context-types.ts
index 1991ed38ef..559f08bc4f 100644
--- a/src/web/types/context-types.ts
+++ b/src/web/types/context-types.ts
@@ -49,4 +49,5 @@ export interface MapLayerAccordionStates {
 export const enum PanelNames {
     NONE = "none",
     MISSIONS = "missions",
+    WAYPOINT = "waypoint",
 }
diff --git a/src/web/types/jaia-system-types.ts b/src/web/types/jaia-system-types.ts
index 73a752106e..5cde655130 100644
--- a/src/web/types/jaia-system-types.ts
+++ b/src/web/types/jaia-system-types.ts
@@ -1,4 +1,9 @@
-import { MissionState } from "./protobuf-types";
+import {
+    ConstantHeadingParameters,
+    DiveParameters,
+    DriftParameters,
+    MissionState,
+} from "./protobuf-types";
 
 export enum NodeTypes {
     NONE = "NONE",
@@ -11,9 +16,35 @@ export interface SelectedNode {
     id: number;
 }
 
+export interface SelectedWaypoint {
+    waypointNum: number;
+    missionID: number;
+}
+
 export interface MissionStatus {
     missionState?: MissionState;
     activeGoal?: number;
     distanceToActiveGoal?: number;
     repeatIndex?: number;
 }
+
+export interface TaskParameters {
+    dive: DiveParameters;
+    drift: DriftParameters;
+    constantHeading: ConstantHeadingParameters;
+}
+
+export enum TaskParameterKeys {
+    MAX_DEPTH = "MAX_DEPTH",
+    DEPTH_INTERVAL = "DEPTH_INTERVAL",
+    HOLD_TIME = "HOLD_TIME",
+    DRIFT_TIME = "DRIFT_TIME",
+    HEADING = "HEADING",
+    CONSTANT_HEADING_TIME = "CONSTANT_HEADING_TIME",
+    SPEED = "SPEED",
+}
+
+export interface TaskParameterPair {
+    key: TaskParameterKeys;
+    value: number;
+}
diff --git a/src/web/utils/constants.ts b/src/web/utils/constants.ts
index 0b93fcf047..bd952fa063 100644
--- a/src/web/utils/constants.ts
+++ b/src/web/utils/constants.ts
@@ -1,6 +1,7 @@
 export const UNASSIGNED_ID = -1;
 export const DEFAULT_HUB_ID = 1;
 export const DETAILS_DECIMALS = 2;
+export const LAT_LON_DECIMALS = 5;
 export const TEXT_OFFSET_RADIUS = 11;
 export const NO_COMMS_STATUS_AGE = 30; // seconds
 export const DATA_MODEL_POLL_TIME = 500; // milliseconds
diff --git a/src/web/utils/input.ts b/src/web/utils/input.ts
new file mode 100644
index 0000000000..e0bdaf2cd6
--- /dev/null
+++ b/src/web/utils/input.ts
@@ -0,0 +1,10 @@
+/**
+ * Removes leading zero from numerical input. For example,
+ * 010 will be returned as 10.
+ *
+ * @param {number} input Number with a unnecessary leading zero
+ * @returns {string} Numerical input without leading zero
+ */
+export function formatNumericalInput(input: number) {
+    return Number(input).toString();
+}


Reviews:
user: login: JRoybot
body: I did a static review of the code.  It's a lot to absorb.  Made a few suggestions that may spur some discussions.  I will return to review again and build and test the branch before making a final approval or request for changes
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T14:24:46Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T19:02:39Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T19:12:15Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T19:15:22Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T19:18:24Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T19:21:02Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T19:22:54Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T19:34:05Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T19:34:31Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T19:47:48Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T21:02:21Z
user: login: JRoybot
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-13T12:32:07Z
user: login: JRoybot
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-13T12:34:55Z
user: login: JRoybot
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-13T13:25:16Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-13T13:46:47Z
user: login: JRoybot
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-13T18:33:29Z
user: login: JRoybot
body: This all looks good, left a few minor comments and questions.  Would like to have seen some tests included in the merge
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T13:28:26Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:03:02Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:03:57Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:13:46Z
user: login: JRoybot
body: Looks good!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-20T17:41:18Z
user: login: michael-jaia
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T17:57:06Z

Review comments:
diff_hunk: @@ -309,6 +335,69 @@ function handleAddWaypoint(mutableState: JaiaContextType, location: GeographicCo
     return mutableState;
 }
 
+/**
+ * Makes call to remove a waypoint from a mission
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleDeleteWaypoint(mutableState: JaiaContextType) {
path: src/web/context/JaiaContext.tsx
user: login: JRoybot
body: There is an inconsistency between this pattern and `handleDeleteMission`.  
In `handleDeleteMission` the mission to be deleted is passed in as `missionID` based on the `.missionID` from the ` JaiaAction`    The action includes the `clickedWaypoint` attribute.  Is there a reason we are not putting the ID in the action and using it here instead of calling `getSelectedWaypoint()`
created_at: 2025-05-12T13:02:17Z
author_association: COLLABORATOR
diff_hunk: @@ -469,6 +572,22 @@ function handleClickedPanelButton(mutableState: JaiaContextType, panelName: Pane
     return mutableState;
 }
 
+/**
+ * Opens panel for the selected waypoint
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @param {SelectedWaypoint} clickedWaypoint Identifies which waypoint was clicked by operator
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleClickedWaypoint(mutableState: JaiaContextType, clickedWaypoint: SelectedWaypoint) {
+    jaiaGlobal.setSelectedWaypoint(clickedWaypoint);
path: src/web/context/JaiaContext.tsx
user: login: JRoybot
body: shouldn't we check if the clicked waypoint is currently selected and deselect if it is?
created_at: 2025-05-12T13:08:49Z
author_association: COLLABORATOR
diff_hunk: @@ -11,9 +11,39 @@ export interface SelectedNode {
     id: number;
 }
 
+export interface SelectedWaypoint {
+    waypointNum: number;
+    missionID: number;
path: src/web/types/jaia-system-types.ts
user: login: JRoybot
body: a little concerned we have a slight circular reference because waypoints are inherently part of a mission in the data model and there is a chance of these IDs being out of sync with how the data in the data model is managed.  I presume this is included here because that relationship between the waypoint and a mission does not exist for the waypoint feature in the map?  Not sure of a better way to handle this but bringing it up to spur conversation....
created_at: 2025-05-12T13:19:01Z
author_association: COLLABORATOR
diff_hunk: @@ -309,6 +335,69 @@ function handleAddWaypoint(mutableState: JaiaContextType, location: GeographicCo
     return mutableState;
 }
 
+/**
+ * Makes call to remove a waypoint from a mission
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleDeleteWaypoint(mutableState: JaiaContextType) {
+    const mission = missions.getMission(jaiaGlobal.getSelectedWaypoint().missionID);
+    mission.deleteWaypoint(jaiaGlobal.getSelectedWaypoint().waypointNum);
+    jaiaGlobal.setSelectedWaypoint({ waypointNum: UNASSIGNED_ID, missionID: UNASSIGNED_ID });
+
+    mutableState.missions = missions.getMissions();
+    mutableState.selectedWaypoint = jaiaGlobal.getSelectedWaypoint();
+    mutableState.visiblePanel = PanelNames.NONE;
+
+    missionLayer.updateFeatures();
+
+    return mutableState;
+}
+
+/**
+ * Updates the task associated with a waypoint based on the operator's selection
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @param {TaskType} taskType Name of the task selected
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleSelectTask(mutableState: JaiaContextType, taskType: TaskType) {
+    const waypoint = getWaypoint();
path: src/web/context/JaiaContext.tsx
user: login: JRoybot
body: similar to comment for deleteWaypoint.... is there a reason we do not pass the waypoint in the action data?
created_at: 2025-05-12T13:22:03Z
author_association: COLLABORATOR
diff_hunk: @@ -309,6 +335,69 @@ function handleAddWaypoint(mutableState: JaiaContextType, location: GeographicCo
     return mutableState;
 }
 
+/**
+ * Makes call to remove a waypoint from a mission
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleDeleteWaypoint(mutableState: JaiaContextType) {
+    const mission = missions.getMission(jaiaGlobal.getSelectedWaypoint().missionID);
+    mission.deleteWaypoint(jaiaGlobal.getSelectedWaypoint().waypointNum);
+    jaiaGlobal.setSelectedWaypoint({ waypointNum: UNASSIGNED_ID, missionID: UNASSIGNED_ID });
+
+    mutableState.missions = missions.getMissions();
+    mutableState.selectedWaypoint = jaiaGlobal.getSelectedWaypoint();
+    mutableState.visiblePanel = PanelNames.NONE;
+
+    missionLayer.updateFeatures();
+
+    return mutableState;
+}
+
+/**
+ * Updates the task associated with a waypoint based on the operator's selection
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @param {TaskType} taskType Name of the task selected
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleSelectTask(mutableState: JaiaContextType, taskType: TaskType) {
+    const waypoint = getWaypoint();
+    const task = waypoint.getTask();
+
+    if (task) {
+        task.setType(taskType);
+    } else {
+        const newTask = new Task();
+        newTask.setType(taskType);
+        waypoint.setTask(newTask);
+    }
+
+    missionLayer.updateFeatures();
+
+    return mutableState;
+}
+
+/**
+ * Makes call to update the parameters of a task based on user input
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @param {TaskParameterPair} taskParameterPair The name of the input updated and its value
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleChangeTaskParameter(
+    mutableState: JaiaContextType,
+    taskParameterPair: TaskParameterPair,
+) {
+    const waypoint = getWaypoint();
path: src/web/context/JaiaContext.tsx
user: login: JRoybot
body: same comment as above regarding waypoint 
created_at: 2025-05-12T13:22:32Z
author_association: COLLABORATOR
diff_hunk: @@ -320,6 +409,20 @@ function handleClosedDetails(mutableState: JaiaContextType) {
     return mutableState;
 }
 
+/**
+ * Handles cleanup when a waypoint panel closes
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleClosedWaypointPanel(mutableState: JaiaContextType) {
+    jaiaGlobal.setSelectedWaypoint({ waypointNum: UNASSIGNED_ID, missionID: UNASSIGNED_ID });
+
+    mutableState.selectedWaypoint = jaiaGlobal.getSelectedWaypoint();
path: src/web/context/JaiaContext.tsx
user: login: JRoybot
body: same comment regarding waypoint
created_at: 2025-05-12T13:23:29Z
author_association: COLLABORATOR
diff_hunk: @@ -1,15 +1,37 @@
 import { TaskType } from "../../types/protobuf-types";
-import ConstantHeadingParameters from "./parameters/constant-heading-parameters";
-import DiveParameters from "./parameters/dive-parameters";
-import DriftParameters from "./parameters/drift-parameters";
-import StationKeepParameters from "./parameters/station-keep-parameters";
+import { TaskParameterKeys, TaskParameterPair } from "../../types/jaia-system-types";
+import { jaiaGlobal } from "../jaia_global/jaia-global";
 
 export default class Task {
     private type: TaskType;
-    private diveParameters: DiveParameters;
-    private driftParameters: DriftParameters;
-    private constantHeadingParameters: ConstantHeadingParameters;
-    private stationKeepParameters: StationKeepParameters;
+
+    // Dive Parameters
path: src/web/data/tasks/task.ts
user: login: JRoybot
body: I wish the data structure used for tasks enforced which parameters went with which TaskType, both missing paramaters for a task type and rogue parameters that should not be associated with a task type have been sources of errors that were hard to debug.  I had to write a very exhaustive tests for the previous TaskSettingsPanel to make sure it didn't produce bogus tasks.  I would like to discuss any options to reduce the potential for ill-formed tasks being created and saved.  Maybe use subclasses or unions?
created_at: 2025-05-12T13:37:03Z
author_association: COLLABORATOR
diff_hunk: @@ -1,15 +1,37 @@
 import { TaskType } from "../../types/protobuf-types";
-import ConstantHeadingParameters from "./parameters/constant-heading-parameters";
-import DiveParameters from "./parameters/dive-parameters";
-import DriftParameters from "./parameters/drift-parameters";
-import StationKeepParameters from "./parameters/station-keep-parameters";
+import { TaskParameterKeys, TaskParameterPair } from "../../types/jaia-system-types";
+import { jaiaGlobal } from "../jaia_global/jaia-global";
 
 export default class Task {
     private type: TaskType;
-    private diveParameters: DiveParameters;
-    private driftParameters: DriftParameters;
-    private constantHeadingParameters: ConstantHeadingParameters;
-    private stationKeepParameters: StationKeepParameters;
+
+    // Dive Parameters
+    private maxDepth: number;
+    private depthInterval: number;
+    private holdTime: number;
+
+    // Drift Parameters
+    private driftTime: number;
+
+    // Constant Heading Parameters
+    private heading: number;
+    private constantHeadingTime: number;
+    private speed: number;
+
+    // Parameter Constraints
+    MIN_DEPTH = 0;
path: src/web/data/tasks/task.ts
user: login: JRoybot
body: Using MAX_DEPTH for the constraint is confusing because there is a task parameter of the same name.  Suggest using either "limit" or "constraing" in the names of these parameter constraints.
created_at: 2025-05-12T13:49:54Z
author_association: COLLABORATOR
diff_hunk: @@ -18,40 +40,182 @@ export default class Task {
         return this.type;
     }
 
+    /**
+     * Initializes the task parameters when an operator selects a new task
+     *
+     * @param {TaskType} type Name of selected task
+     * @returns {void}
+     */
     setType(type: TaskType) {
+        const defaultParams = jaiaGlobal.getDefaultTaskParameters();
+
+        switch (type) {
+            case TaskType.DIVE:
+                this.maxDepth = defaultParams.maxDepth;
+                this.depthInterval = defaultParams.depthInterval;
+                this.holdTime = defaultParams.holdTime;
+                this.driftTime = defaultParams.driftTime;
+                break;
+            case TaskType.SURFACE_DRIFT:
+                this.driftTime = defaultParams.driftTime;
+                break;
+            case TaskType.CONSTANT_HEADING:
+                this.heading = defaultParams.heading;
+                this.constantHeadingTime = defaultParams.constantHeadingTime;
+                this.speed = defaultParams.speed;
+                break;
+        }
+
         this.type = type;
     }
 
-    getDiveParameters() {
-        return this.diveParameters;
+    /**
+     * Handles inputs made by the operator to change task parameters
+     *
+     * @param {TaskParameterPair} taskParameterPair Name of parameter + its value
+     * @returns {void}
+     */
+    setParameter(taskParameterPair: TaskParameterPair) {
+        const key = taskParameterPair.key;
+        let value = taskParameterPair.value;
+        const mutableDefaults = { ...jaiaGlobal.getDefaultTaskParameters() };
+
+        switch (key) {
+            case TaskParameterKeys.MAX_DEPTH:
+                this.setMaxDepth(value);
+                mutableDefaults.maxDepth = value;
path: src/web/data/tasks/task.ts
user: login: JRoybot
body: since `setMaxDepth` might override the `value` with the constraints I think this should be `this.maxDepth`
created_at: 2025-05-12T13:52:11Z
author_association: COLLABORATOR
diff_hunk: @@ -22,10 +28,49 @@ export default function App() {
                 <NodeList />
                 <TopButtonList />
                 <SideButtonList />
-                <HubDetails />
-                <BotDetails />
-                <MissionsPanel />
+                <Details />
+                <Panel />
             </JaiaContextProvider>
         </div>
     );
 }
+
+/**
+ * Controls the rendering of HubDetails and BotDetails
+ */
+function Details() {
path: src/web/jcc/App.tsx
user: login: JRoybot
body: Would this function be better off as a container with the BotDetails and HubDetails nested in the directory structure?
created_at: 2025-05-12T14:02:19Z
author_association: COLLABORATOR
diff_hunk: @@ -22,10 +28,49 @@ export default function App() {
                 <NodeList />
                 <TopButtonList />
                 <SideButtonList />
-                <HubDetails />
-                <BotDetails />
-                <MissionsPanel />
+                <Details />
+                <Panel />
             </JaiaContextProvider>
         </div>
     );
 }
+
+/**
+ * Controls the rendering of HubDetails and BotDetails
+ */
+function Details() {
+    const jaiaContext = useContext(JaiaContext);
+
+    if (jaiaContext === null) {
+        return <div></div>;
+    }
+
+    switch (jaiaContext.visibleDetails) {
+        case NodeTypes.HUB:
+            return <HubDetails />;
+        case NodeTypes.BOT:
+            return <BotDetails />;
+        default:
+            return <div></div>;
+    }
+}
+
+/**
+ *  Controls the rendering of JCC panels
+ */
+function Panel() {
path: src/web/jcc/App.tsx
user: login: JRoybot
body: Can this function get a more descriptive name?  "OverlayPanel" , "ActionPanel", "PopUpPanel"??
created_at: 2025-05-12T14:03:41Z
author_association: COLLABORATOR
diff_hunk: @@ -7,12 +7,17 @@ export enum JaiaActions {
     ASSIGN_MISSION = "ASSIGN_MISSION",
     AUTO_ASSIGN_MISSIONS = "AUTO_ASSIGN_MISSIONS",
     ADD_WAYPOINT = "ADD_WAYPOINT",
+    DELETE_WAYPOINT = "DELETE_WAYPOINT",
+    SELECT_TASK = "SELECT_TASK",
+    CHANGE_TASK_PARAMETER = "CHANGE_TASK_PARAMETER",
 
     CLOSED_DETAILS = "CLOSED_DETAILS",
+    CLOSED_WAYPOINT_PANEL = "CLOSED_WAYPOINT_PANEL",
path: src/web/context/jaia-actions.ts
user: login: JRoybot
body: Since we are going to be adding more panels, could we have a single action to close whatever transient panel is displayed?  Should be similar to how `CLICKED_PANEL_BUTTON` opens a panel.  We should add the panel name in the action data (or just use `jaiaContext.visiblePanel`, passing it is  more consistent with existing functions) 
 Again, would like to see the group of panels managed as "Panel" in the jcc app have a more descriptive name.
created_at: 2025-05-12T14:09:45Z
author_association: COLLABORATOR
diff_hunk: @@ -1,27 +1,62 @@
-import { NodeTypes, SelectedNode } from "../../types/jaia-system-types";
+import {
+    NodeTypes,
+    SelectedNode,
+    SelectedWaypoint,
+    TaskParameters,
+} from "../../types/jaia-system-types";
 import { UNASSIGNED_ID } from "../../utils/constants";
 
+const defaultTaskParameters: TaskParameters = {
+    maxDepth: 10,
+    depthInterval: 10,
+    holdTime: 0,
+    driftTime: 0,
+    heading: 180,
+    constantHeadingTime: 30,
+    speed: 2,
+};
+
 class JaiaGlobal {
     private selectedNode: SelectedNode;
+    private selectedWaypoint: SelectedWaypoint;
+    private defaultTaskParameters: TaskParameters;
 
     constructor() {
         this.selectedNode = { type: NodeTypes.NONE, id: UNASSIGNED_ID };
+        this.selectedWaypoint = { waypointNum: UNASSIGNED_ID, missionID: UNASSIGNED_ID };
+        this.defaultTaskParameters = defaultTaskParameters;
     }
 
     getSelectedNode() {
         return this.selectedNode;
     }
 
-    setSelectedNode(selectedNode: SelectedNode) {
+    setSelectedNode(clickedNode: SelectedNode) {
         if (
-            selectedNode.type === this.getSelectedNode().type &&
-            selectedNode.id === this.getSelectedNode().id
+            clickedNode.type === this.getSelectedNode().type &&
+            clickedNode.id === this.getSelectedNode().id
         ) {
             this.selectedNode = { type: NodeTypes.NONE, id: UNASSIGNED_ID };
         } else {
-            this.selectedNode = selectedNode;
+            this.selectedNode = clickedNode;
         }
     }
+
+    getSelectedWaypoint() {
+        return this.selectedWaypoint;
+    }
+
+    setSelectedWaypoint(clickedWaypoint: SelectedWaypoint) {
+        this.selectedWaypoint = clickedWaypoint;
path: src/web/data/jaia_global/jaia-global.ts
user: login: JRoybot
body: I don't see anywhere in the call stack where the waypoint ID is checked to make sure it exists in the mission... not likely but what would happen if the selected waypoint got set to a waypoint that doesnt exist?
created_at: 2025-05-12T14:17:31Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,137 @@
+import { useContext, useEffect } from "react";
+
+import TaskParameters from "../TaskParameters/TaskParameters";
+import { JaiaContext, JaiaDispatchContext } from "../../context/JaiaContext";
+import { JaiaActions } from "../../context/jaia-actions";
+import { missionsManager } from "../../data/missions_manager/missions-manager";
+import { TaskType } from "../../types/protobuf-types";
+
+import { UNASSIGNED_ID, LAT_LON_DECIMALS } from "../../utils/constants";
+
+import Icon from "@mdi/react";
+import { mdiDelete } from "@mdi/js";
+import { Button, FormControl, Select, MenuItem, SelectChangeEvent } from "@mui/material";
+
+import "./WaypointPanel.less";
+
+export default function WaypointPanel() {
+    const jaiaContext = useContext(JaiaContext);
+    const jaiaDispatchContext = useContext(JaiaDispatchContext);
+
+    useEffect(() => {
+        return () => {
+            jaiaDispatchContext({ type: JaiaActions.CLOSED_WAYPOINT_PANEL });
+        };
+    }, []);
+
+    const getWaypoint = () => {
+        const mission = jaiaContext.missions.get(jaiaContext.selectedWaypoint.missionID);
+        return mission.getWaypoint(jaiaContext.selectedWaypoint.waypointNum);
+    };
+
+    const getTaskType = () => {
+        const taskType = getWaypoint().getTask()?.getType();
+
+        if (!taskType) {
+            return TaskType.NONE;
+        }
+
+        return taskType;
+    };
+
+    const formatBotID = () => {
+        const botID = missionsManager.getBotID(jaiaContext.selectedWaypoint.missionID);
+
+        if (botID === UNASSIGNED_ID) {
+            return "";
+        }
+
+        return botID;
+    };
+
+    const formatMenuItemText = (taskType: TaskType) => {
+        switch (taskType) {
+            case TaskType.NONE:
+                return "None";
+            case TaskType.DIVE:
+                return "Dive";
+            case TaskType.SURFACE_DRIFT:
+                return "Surface Drift";
+            case TaskType.STATION_KEEP:
+                return "Station Keep";
+            case TaskType.CONSTANT_HEADING:
+                return "Constant Heading";
+            default:
+                return "";
+        }
+    };
+
+    const handleDeleteWaypointClick = () => {
+        jaiaDispatchContext({ type: JaiaActions.DELETE_WAYPOINT });
path: src/web/components/WaypointPanel/WaypointPanel.tsx
user: login: JRoybot
body: suggest including waypointID in the action data
created_at: 2025-05-12T14:20:57Z
author_association: COLLABORATOR
diff_hunk: @@ -469,6 +572,22 @@ function handleClickedPanelButton(mutableState: JaiaContextType, panelName: Pane
     return mutableState;
 }
 
+/**
+ * Opens panel for the selected waypoint
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @param {SelectedWaypoint} clickedWaypoint Identifies which waypoint was clicked by operator
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleClickedWaypoint(mutableState: JaiaContextType, clickedWaypoint: SelectedWaypoint) {
+    jaiaGlobal.setSelectedWaypoint(clickedWaypoint);
path: src/web/context/JaiaContext.tsx
user: login: michael-jaia
body: Maybe down the road, but that is not how it is implemented in the current version of the JCC
created_at: 2025-05-12T19:02:39Z
author_association: COLLABORATOR
diff_hunk: @@ -18,40 +40,182 @@ export default class Task {
         return this.type;
     }
 
+    /**
+     * Initializes the task parameters when an operator selects a new task
+     *
+     * @param {TaskType} type Name of selected task
+     * @returns {void}
+     */
     setType(type: TaskType) {
+        const defaultParams = jaiaGlobal.getDefaultTaskParameters();
+
+        switch (type) {
+            case TaskType.DIVE:
+                this.maxDepth = defaultParams.maxDepth;
+                this.depthInterval = defaultParams.depthInterval;
+                this.holdTime = defaultParams.holdTime;
+                this.driftTime = defaultParams.driftTime;
+                break;
+            case TaskType.SURFACE_DRIFT:
+                this.driftTime = defaultParams.driftTime;
+                break;
+            case TaskType.CONSTANT_HEADING:
+                this.heading = defaultParams.heading;
+                this.constantHeadingTime = defaultParams.constantHeadingTime;
+                this.speed = defaultParams.speed;
+                break;
+        }
+
         this.type = type;
     }
 
-    getDiveParameters() {
-        return this.diveParameters;
+    /**
+     * Handles inputs made by the operator to change task parameters
+     *
+     * @param {TaskParameterPair} taskParameterPair Name of parameter + its value
+     * @returns {void}
+     */
+    setParameter(taskParameterPair: TaskParameterPair) {
+        const key = taskParameterPair.key;
+        let value = taskParameterPair.value;
+        const mutableDefaults = { ...jaiaGlobal.getDefaultTaskParameters() };
+
+        switch (key) {
+            case TaskParameterKeys.MAX_DEPTH:
+                this.setMaxDepth(value);
+                mutableDefaults.maxDepth = value;
path: src/web/data/tasks/task.ts
user: login: michael-jaia
body: Yes, good catch

created_at: 2025-05-12T19:12:15Z
author_association: COLLABORATOR
diff_hunk: @@ -22,10 +28,49 @@ export default function App() {
                 <NodeList />
                 <TopButtonList />
                 <SideButtonList />
-                <HubDetails />
-                <BotDetails />
-                <MissionsPanel />
+                <Details />
+                <Panel />
             </JaiaContextProvider>
         </div>
     );
 }
+
+/**
+ * Controls the rendering of HubDetails and BotDetails
+ */
+function Details() {
path: src/web/jcc/App.tsx
user: login: michael-jaia
body: I like storing which panels render in App.tsx. I see App.tsx as the place for that type of control where the other directories should just worry about creating the component, not control when it renders.
created_at: 2025-05-12T19:15:22Z
author_association: COLLABORATOR
diff_hunk: @@ -22,10 +28,49 @@ export default function App() {
                 <NodeList />
                 <TopButtonList />
                 <SideButtonList />
-                <HubDetails />
-                <BotDetails />
-                <MissionsPanel />
+                <Details />
+                <Panel />
             </JaiaContextProvider>
         </div>
     );
 }
+
+/**
+ * Controls the rendering of HubDetails and BotDetails
+ */
+function Details() {
+    const jaiaContext = useContext(JaiaContext);
+
+    if (jaiaContext === null) {
+        return <div></div>;
+    }
+
+    switch (jaiaContext.visibleDetails) {
+        case NodeTypes.HUB:
+            return <HubDetails />;
+        case NodeTypes.BOT:
+            return <BotDetails />;
+        default:
+            return <div></div>;
+    }
+}
+
+/**
+ *  Controls the rendering of JCC panels
+ */
+function Panel() {
path: src/web/jcc/App.tsx
user: login: michael-jaia
body: I left it general because all panels outside of the "details" panels will be included in the function. Of the three suggestions I like overlay panel the best, but I also feel like it doesn't add much description since all of the panels will be overlay panels.
created_at: 2025-05-12T19:18:24Z
author_association: COLLABORATOR
diff_hunk: @@ -1,27 +1,62 @@
-import { NodeTypes, SelectedNode } from "../../types/jaia-system-types";
+import {
+    NodeTypes,
+    SelectedNode,
+    SelectedWaypoint,
+    TaskParameters,
+} from "../../types/jaia-system-types";
 import { UNASSIGNED_ID } from "../../utils/constants";
 
+const defaultTaskParameters: TaskParameters = {
+    maxDepth: 10,
+    depthInterval: 10,
+    holdTime: 0,
+    driftTime: 0,
+    heading: 180,
+    constantHeadingTime: 30,
+    speed: 2,
+};
+
 class JaiaGlobal {
     private selectedNode: SelectedNode;
+    private selectedWaypoint: SelectedWaypoint;
+    private defaultTaskParameters: TaskParameters;
 
     constructor() {
         this.selectedNode = { type: NodeTypes.NONE, id: UNASSIGNED_ID };
+        this.selectedWaypoint = { waypointNum: UNASSIGNED_ID, missionID: UNASSIGNED_ID };
+        this.defaultTaskParameters = defaultTaskParameters;
     }
 
     getSelectedNode() {
         return this.selectedNode;
     }
 
-    setSelectedNode(selectedNode: SelectedNode) {
+    setSelectedNode(clickedNode: SelectedNode) {
         if (
-            selectedNode.type === this.getSelectedNode().type &&
-            selectedNode.id === this.getSelectedNode().id
+            clickedNode.type === this.getSelectedNode().type &&
+            clickedNode.id === this.getSelectedNode().id
         ) {
             this.selectedNode = { type: NodeTypes.NONE, id: UNASSIGNED_ID };
         } else {
-            this.selectedNode = selectedNode;
+            this.selectedNode = clickedNode;
         }
     }
+
+    getSelectedWaypoint() {
+        return this.selectedWaypoint;
+    }
+
+    setSelectedWaypoint(clickedWaypoint: SelectedWaypoint) {
+        this.selectedWaypoint = clickedWaypoint;
path: src/web/data/jaia_global/jaia-global.ts
user: login: michael-jaia
body: I'm not sure if it is worth coupling the Mission class with JaiaGlobal. I like how JaiaGlobal just focuses on handling important UI data that comes from user interactions. That data should only come from the click event. If we get the wrong data part of me feels like we have bigger problems. This is just my opinion. Tell me if you think this is too loose. I could add docstrings to each of the functions to make this clear as well.
created_at: 2025-05-12T19:21:02Z
author_association: COLLABORATOR
diff_hunk: @@ -1,15 +1,37 @@
 import { TaskType } from "../../types/protobuf-types";
-import ConstantHeadingParameters from "./parameters/constant-heading-parameters";
-import DiveParameters from "./parameters/dive-parameters";
-import DriftParameters from "./parameters/drift-parameters";
-import StationKeepParameters from "./parameters/station-keep-parameters";
+import { TaskParameterKeys, TaskParameterPair } from "../../types/jaia-system-types";
+import { jaiaGlobal } from "../jaia_global/jaia-global";
 
 export default class Task {
     private type: TaskType;
-    private diveParameters: DiveParameters;
-    private driftParameters: DriftParameters;
-    private constantHeadingParameters: ConstantHeadingParameters;
-    private stationKeepParameters: StationKeepParameters;
+
+    // Dive Parameters
+    private maxDepth: number;
+    private depthInterval: number;
+    private holdTime: number;
+
+    // Drift Parameters
+    private driftTime: number;
+
+    // Constant Heading Parameters
+    private heading: number;
+    private constantHeadingTime: number;
+    private speed: number;
+
+    // Parameter Constraints
+    MIN_DEPTH = 0;
path: src/web/data/tasks/task.ts
user: login: michael-jaia
body: I thought the capitalization would distinguish them enough, but if that is not the case, we can use CONSTRAINT at the end of the variable names

created_at: 2025-05-12T19:22:54Z
author_association: COLLABORATOR
diff_hunk: @@ -1,15 +1,37 @@
 import { TaskType } from "../../types/protobuf-types";
-import ConstantHeadingParameters from "./parameters/constant-heading-parameters";
-import DiveParameters from "./parameters/dive-parameters";
-import DriftParameters from "./parameters/drift-parameters";
-import StationKeepParameters from "./parameters/station-keep-parameters";
+import { TaskParameterKeys, TaskParameterPair } from "../../types/jaia-system-types";
+import { jaiaGlobal } from "../jaia_global/jaia-global";
 
 export default class Task {
     private type: TaskType;
-    private diveParameters: DiveParameters;
-    private driftParameters: DriftParameters;
-    private constantHeadingParameters: ConstantHeadingParameters;
-    private stationKeepParameters: StationKeepParameters;
+
+    // Dive Parameters
path: src/web/data/tasks/task.ts
user: login: michael-jaia
body: I think we can find a way to do this. Let's talk this one over tomorrow.

created_at: 2025-05-12T19:34:05Z
author_association: COLLABORATOR
diff_hunk: @@ -309,6 +335,69 @@ function handleAddWaypoint(mutableState: JaiaContextType, location: GeographicCo
     return mutableState;
 }
 
+/**
+ * Makes call to remove a waypoint from a mission
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleDeleteWaypoint(mutableState: JaiaContextType) {
path: src/web/context/JaiaContext.tsx
user: login: michael-jaia
body: The reason I did this is because context does not know which mission the operator is trying to delete unless data is passed via the action option. With a waypoint, only one waypoint can be deleted and that is the selected waypoint which is already stored in context. To me, passing the waypoint through the action here would be unnecessary since the data already lives in context. Feel free to push back on this
created_at: 2025-05-12T19:34:31Z
author_association: COLLABORATOR
diff_hunk: @@ -11,9 +11,39 @@ export interface SelectedNode {
     id: number;
 }
 
+export interface SelectedWaypoint {
+    waypointNum: number;
+    missionID: number;
path: src/web/types/jaia-system-types.ts
user: login: michael-jaia
body: I'm not quite following the circular reference. I see the SelectedWaypoint as similar to a SelectedNode. It's not a core data model feature, it's more of a UI helper object. 
created_at: 2025-05-12T19:47:48Z
author_association: COLLABORATOR
diff_hunk: @@ -7,12 +7,17 @@ export enum JaiaActions {
     ASSIGN_MISSION = "ASSIGN_MISSION",
     AUTO_ASSIGN_MISSIONS = "AUTO_ASSIGN_MISSIONS",
     ADD_WAYPOINT = "ADD_WAYPOINT",
+    DELETE_WAYPOINT = "DELETE_WAYPOINT",
+    SELECT_TASK = "SELECT_TASK",
+    CHANGE_TASK_PARAMETER = "CHANGE_TASK_PARAMETER",
 
     CLOSED_DETAILS = "CLOSED_DETAILS",
+    CLOSED_WAYPOINT_PANEL = "CLOSED_WAYPOINT_PANEL",
path: src/web/context/jaia-actions.ts
user: login: michael-jaia
body: I think we can do this. I think this would be better as its own ticket.
created_at: 2025-05-12T21:02:21Z
author_association: COLLABORATOR
diff_hunk: @@ -309,6 +335,69 @@ function handleAddWaypoint(mutableState: JaiaContextType, location: GeographicCo
     return mutableState;
 }
 
+/**
+ * Makes call to remove a waypoint from a mission
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleDeleteWaypoint(mutableState: JaiaContextType) {
path: src/web/context/JaiaContext.tsx
user: login: JRoybot
body: I understand the reasoning.  So in some cases the mission being deleted will not be the `missionIDInEditMode` stored in context?  I didn't consider that scenario.  My concern is there are multiple sets of data defining what mission is being edited and was concerned that a race condition may exist where they are not in sync.  
created_at: 2025-05-13T12:32:07Z
author_association: COLLABORATOR
diff_hunk: @@ -22,10 +28,49 @@ export default function App() {
                 <NodeList />
                 <TopButtonList />
                 <SideButtonList />
-                <HubDetails />
-                <BotDetails />
-                <MissionsPanel />
+                <Details />
+                <Panel />
             </JaiaContextProvider>
         </div>
     );
 }
+
+/**
+ * Controls the rendering of HubDetails and BotDetails
+ */
+function Details() {
path: src/web/jcc/App.tsx
user: login: JRoybot
body: OK
created_at: 2025-05-13T12:34:55Z
author_association: COLLABORATOR
diff_hunk: @@ -1,15 +1,37 @@
 import { TaskType } from "../../types/protobuf-types";
-import ConstantHeadingParameters from "./parameters/constant-heading-parameters";
-import DiveParameters from "./parameters/dive-parameters";
-import DriftParameters from "./parameters/drift-parameters";
-import StationKeepParameters from "./parameters/station-keep-parameters";
+import { TaskParameterKeys, TaskParameterPair } from "../../types/jaia-system-types";
+import { jaiaGlobal } from "../jaia_global/jaia-global";
 
 export default class Task {
     private type: TaskType;
-    private diveParameters: DiveParameters;
-    private driftParameters: DriftParameters;
-    private constantHeadingParameters: ConstantHeadingParameters;
-    private stationKeepParameters: StationKeepParameters;
+
path: src/web/data/tasks/task.ts
user: login: JRoybot
body: To accommodate bottom dives we need a way to indicate that.   In the old code it was just a boolean flag, however that contributed to the issues of having/missing parameters that do not match the type.  An alternative to that would be to have two distinct task types for interval dives vs bottom dives.  Either way we want to make sure we consider that when discussing how to manage the parameters.
created_at: 2025-05-13T13:25:16Z
author_association: COLLABORATOR
diff_hunk: @@ -309,6 +335,69 @@ function handleAddWaypoint(mutableState: JaiaContextType, location: GeographicCo
     return mutableState;
 }
 
+/**
+ * Makes call to remove a waypoint from a mission
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleDeleteWaypoint(mutableState: JaiaContextType) {
path: src/web/context/JaiaContext.tsx
user: login: JRoybot
body: Is the only other way to delete waypoints deleting the entire mission?
created_at: 2025-05-13T13:26:53Z
author_association: COLLABORATOR
diff_hunk: @@ -309,6 +335,69 @@ function handleAddWaypoint(mutableState: JaiaContextType, location: GeographicCo
     return mutableState;
 }
 
+/**
+ * Makes call to remove a waypoint from a mission
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleDeleteWaypoint(mutableState: JaiaContextType) {
path: src/web/context/JaiaContext.tsx
user: login: michael-jaia
body: * So in some cases the mission being deleted will not be the missionIDInEditMode stored in context?
    * Yes, even when a mission is not in edit mode, the delete buttons still works in the missions panel.

* My concern is there are multiple sets of data defining what mission is being edited and was concerned that a race condition may exist where they are not in sync.
    * How I see it is there is only one property that defines what mission is being edited and that is `missionIDInEditMode`. This property is only updated in `JaiaContext` and then the data is transferred to the `JaiaGlobal` singleton so OpenLayers can view it. As we continue building out the waypoint panel, there will be a check to see if the waypoint belongs to the mission in edit mode. Whether that check is T or F will dictate what the panel renders (if the waypoint belongs to the mission in edit mode, then there will be an option to delete the waypoint, if it does not, then there will be no option to delete it, modify the task, or change the location).
created_at: 2025-05-13T13:46:47Z
author_association: COLLABORATOR
diff_hunk: @@ -309,6 +335,69 @@ function handleAddWaypoint(mutableState: JaiaContextType, location: GeographicCo
     return mutableState;
 }
 
+/**
+ * Makes call to remove a waypoint from a mission
+ *
+ * @param {JaiaContextType} mutableState State object ref for making modifications
+ * @returns {JaiaContextType} Updated mutable state object
+ */
+function handleDeleteWaypoint(mutableState: JaiaContextType) {
path: src/web/context/JaiaContext.tsx
user: login: JRoybot
body: Sounds good
created_at: 2025-05-13T18:18:33Z
author_association: COLLABORATOR
diff_hunk: @@ -1,27 +1,62 @@
-import { NodeTypes, SelectedNode } from "../../types/jaia-system-types";
+import {
+    NodeTypes,
+    SelectedNode,
+    SelectedWaypoint,
+    TaskParameters,
+} from "../../types/jaia-system-types";
 import { UNASSIGNED_ID } from "../../utils/constants";
 
+const defaultTaskParameters: TaskParameters = {
+    maxDepth: 10,
+    depthInterval: 10,
+    holdTime: 0,
+    driftTime: 0,
+    heading: 180,
+    constantHeadingTime: 30,
+    speed: 2,
+};
+
 class JaiaGlobal {
     private selectedNode: SelectedNode;
+    private selectedWaypoint: SelectedWaypoint;
+    private defaultTaskParameters: TaskParameters;
 
     constructor() {
         this.selectedNode = { type: NodeTypes.NONE, id: UNASSIGNED_ID };
+        this.selectedWaypoint = { waypointNum: UNASSIGNED_ID, missionID: UNASSIGNED_ID };
+        this.defaultTaskParameters = defaultTaskParameters;
     }
 
     getSelectedNode() {
         return this.selectedNode;
     }
 
-    setSelectedNode(selectedNode: SelectedNode) {
+    setSelectedNode(clickedNode: SelectedNode) {
         if (
-            selectedNode.type === this.getSelectedNode().type &&
-            selectedNode.id === this.getSelectedNode().id
+            clickedNode.type === this.getSelectedNode().type &&
+            clickedNode.id === this.getSelectedNode().id
         ) {
             this.selectedNode = { type: NodeTypes.NONE, id: UNASSIGNED_ID };
         } else {
-            this.selectedNode = selectedNode;
+            this.selectedNode = clickedNode;
         }
     }
+
+    getSelectedWaypoint() {
+        return this.selectedWaypoint;
+    }
+
+    setSelectedWaypoint(clickedWaypoint: SelectedWaypoint) {
+        this.selectedWaypoint = clickedWaypoint;
path: src/web/data/jaia_global/jaia-global.ts
user: login: JRoybot
body: OK, might be overly paranoid on this one.  I agree would only happen if there was a bigger problem but would be a good catch if the bigger problem did occur.
created_at: 2025-05-13T18:21:12Z
author_association: COLLABORATOR
diff_hunk: @@ -11,9 +11,39 @@ export interface SelectedNode {
     id: number;
 }
 
+export interface SelectedWaypoint {
+    waypointNum: number;
+    missionID: number;
path: src/web/types/jaia-system-types.ts
user: login: JRoybot
body: got it 
created_at: 2025-05-13T18:32:11Z
author_association: COLLABORATOR
diff_hunk: @@ -78,144 +81,111 @@ export default class Task {
     setParameter(taskParameterPair: TaskParameterPair) {
         const key = taskParameterPair.key;
         let value = taskParameterPair.value;
-        const mutableDefaults = { ...jaiaGlobal.getDefaultTaskParameters() };
 
         switch (key) {
             case TaskParameterKeys.MAX_DEPTH:
-                this.setMaxDepth(value);
-                mutableDefaults.maxDepth = this.maxDepth;
+                value = this.validateInput(
+                    value,
+                    this.MIN_DEPTH_CONSTRAINT,
+                    this.MAX_DEPTH_CONSTRAINT,
+                );
+                this.diveParameters.max_depth = value;
                 break;
             case TaskParameterKeys.DEPTH_INTERVAL:
-                this.setDepthInterval(value);
-                mutableDefaults.depthInterval = this.depthInterval;
+                value = this.validateInput(
+                    value,
+                    this.MIN_DEPTH_CONSTRAINT,
+                    this.MAX_DEPTH_CONSTRAINT,
+                );
+                this.diveParameters.depth_interval = value;
                 break;
             case TaskParameterKeys.HOLD_TIME:
-                this.setHoldTime(value);
-                mutableDefaults.holdTime = this.holdTime;
+                value = this.validateInput(
+                    value,
+                    this.MIN_HOLD_TIME_CONSTRAINT,
+                    this.MAX_HOLD_TIME_CONSTRAINT,
+                );
+                this.diveParameters.hold_time = value;
                 break;
             case TaskParameterKeys.DRIFT_TIME:
-                this.setDriftTime(value);
-                mutableDefaults.driftTime = this.driftTime;
+                value = this.validateInput(
+                    value,
+                    this.MIN_DRIFT_TIME_CONSTRAINT,
+                    this.NO_CONSTRAINT,
+                );
+                this.driftParameters.drift_time = value;
                 break;
             case TaskParameterKeys.HEADING:
-                this.setHeading(value);
-                mutableDefaults.heading = this.heading;
+                value = this.validateInput(
+                    value,
+                    this.MIN_HEADING_CONSTRAINT,
+                    this.MAX_HEADING_CONSTRAINT,
+                );
+                this.constantHeadingParameters.constant_heading = value;
                 break;
             case TaskParameterKeys.CONSTANT_HEADING_TIME:
-                this.setConstantHeadingTime(value);
-                mutableDefaults.constantHeadingTime = this.constantHeadingTime;
+                value = this.validateInput(
+                    value,
+                    this.MIN_CONSTANT_HEADING_TIME_CONSTRAINT,
+                    this.NO_CONSTRAINT,
+                );
+                this.constantHeadingParameters.constant_heading_time = value;
                 break;
             case TaskParameterKeys.SPEED:
-                this.setSpeed(value);
-                mutableDefaults.speed = this.speed;
+                value = this.validateInput(
+                    value,
+                    this.MIN_SPEED_CONSTRAINT,
+                    this.MAX_SPEED_CONSTRAINT,
+                );
+                this.constantHeadingParameters.constant_heading_speed = value;
                 break;
         }
 
-        jaiaGlobal.setDefaultTaskParameters(mutableDefaults);
-    }
-
-    getMaxDepth() {
-        return this.maxDepth;
-    }
-
-    setMaxDepth(maxDepth: number) {
-        if (maxDepth > this.MAX_DEPTH) {
-            maxDepth = this.MAX_DEPTH;
-        }
-
-        if (maxDepth < this.MIN_DEPTH) {
-            maxDepth = this.MIN_DEPTH;
-        }
-
-        this.maxDepth = maxDepth;
-    }
-
-    getDepthInterval() {
-        return this.depthInterval;
-    }
-
-    setDepthInterval(depthInterval: number) {
-        if (depthInterval > this.MAX_DEPTH) {
-            depthInterval = this.MAX_DEPTH;
-        }
-
-        if (depthInterval < this.MIN_DEPTH) {
-            depthInterval = this.MIN_DEPTH;
-        }
-
-        this.depthInterval = depthInterval;
+        this.updateDefaultTaskParameters();
     }
 
-    getHoldTime() {
-        return this.holdTime;
-    }
-
-    setHoldTime(holdTime: number) {
-        if (holdTime > this.MAX_HOLD_TIME) {
-            holdTime = this.MAX_HOLD_TIME;
+    private validateInput(value: number, min: number, max: number) {
+        if (value > max && max !== this.NO_CONSTRAINT) {
+            return max;
         }
 
-        if (holdTime < this.MIN_HOLD_TIME) {
-            holdTime = this.MIN_HOLD_TIME;
+        if (value < min && min !== this.NO_CONSTRAINT) {
+            return min;
         }
 
-        this.holdTime = holdTime;
+        return value;
     }
 
-    getDriftTime() {
-        return this.driftTime;
+    private updateDefaultTaskParameters() {
path: src/web/data/tasks/task.ts
user: login: JRoybot
body: "default" parameters are just whatever was used last, not a true default.  Is that the desired behavior?
created_at: 2025-05-20T13:07:24Z
author_association: COLLABORATOR
diff_hunk: @@ -47,15 +47,20 @@ export default function TaskParameters(props: Props) {
 
 /**
  * Renders input fields for a dive task
+ *
+ * @notes
+ * Number(value).toString() remvoes the leading zero from input fields
path: src/web/components/TaskParameters/TaskParameters.tsx
user: login: JRoybot
body: since this is used in so many places would be a could thing to factor out into it's own helper function
created_at: 2025-05-20T13:20:23Z
author_association: COLLABORATOR
diff_hunk: @@ -7,13 +7,19 @@ import {
 import { UNASSIGNED_ID } from "../../utils/constants";
 
 const defaultTaskParameters: TaskParameters = {
-    maxDepth: 10,
-    depthInterval: 10,
-    holdTime: 0,
-    driftTime: 0,
-    heading: 180,
-    constantHeadingTime: 30,
-    speed: 2,
+    dive: {
path: src/web/data/jaia_global/jaia-global.ts
user: login: JRoybot
body: even if there is no functionality, should include the bottom dive flag for consistency with the protobuf types
created_at: 2025-05-20T13:23:44Z
author_association: COLLABORATOR
diff_hunk: @@ -7,13 +7,19 @@ import {
 import { UNASSIGNED_ID } from "../../utils/constants";
 
 const defaultTaskParameters: TaskParameters = {
-    maxDepth: 10,
-    depthInterval: 10,
-    holdTime: 0,
-    driftTime: 0,
-    heading: 180,
-    constantHeadingTime: 30,
-    speed: 2,
+    dive: {
+        max_depth: 10,
+        depth_interval: 10,
path: src/web/data/jaia_global/jaia-global.ts
user: login: JRoybot
body: still curious to see how these parameters are handled when it is a bottom dive
created_at: 2025-05-20T13:24:19Z
author_association: COLLABORATOR
diff_hunk: @@ -7,13 +7,19 @@ import {
 import { UNASSIGNED_ID } from "../../utils/constants";
 
 const defaultTaskParameters: TaskParameters = {
-    maxDepth: 10,
-    depthInterval: 10,
-    holdTime: 0,
-    driftTime: 0,
-    heading: 180,
-    constantHeadingTime: 30,
-    speed: 2,
+    dive: {
+        max_depth: 10,
+        depth_interval: 10,
path: src/web/data/jaia_global/jaia-global.ts
user: login: michael-jaia
body: When there is a bottom dive, the dive parameters are: 
```
{
    max_depth: 50,
    depth_interval: 50,
    hold_time: 0
}
```
created_at: 2025-05-20T15:03:02Z
author_association: COLLABORATOR
diff_hunk: @@ -7,13 +7,19 @@ import {
 import { UNASSIGNED_ID } from "../../utils/constants";
 
 const defaultTaskParameters: TaskParameters = {
-    maxDepth: 10,
-    depthInterval: 10,
-    holdTime: 0,
-    driftTime: 0,
-    heading: 180,
-    constantHeadingTime: 30,
-    speed: 2,
+    dive: {
path: src/web/data/jaia_global/jaia-global.ts
user: login: michael-jaia
body: No because the bottom dive field is going to be removed from the protobuf message
created_at: 2025-05-20T15:03:57Z
author_association: COLLABORATOR
diff_hunk: @@ -78,144 +81,111 @@ export default class Task {
     setParameter(taskParameterPair: TaskParameterPair) {
         const key = taskParameterPair.key;
         let value = taskParameterPair.value;
-        const mutableDefaults = { ...jaiaGlobal.getDefaultTaskParameters() };
 
         switch (key) {
             case TaskParameterKeys.MAX_DEPTH:
-                this.setMaxDepth(value);
-                mutableDefaults.maxDepth = this.maxDepth;
+                value = this.validateInput(
+                    value,
+                    this.MIN_DEPTH_CONSTRAINT,
+                    this.MAX_DEPTH_CONSTRAINT,
+                );
+                this.diveParameters.max_depth = value;
                 break;
             case TaskParameterKeys.DEPTH_INTERVAL:
-                this.setDepthInterval(value);
-                mutableDefaults.depthInterval = this.depthInterval;
+                value = this.validateInput(
+                    value,
+                    this.MIN_DEPTH_CONSTRAINT,
+                    this.MAX_DEPTH_CONSTRAINT,
+                );
+                this.diveParameters.depth_interval = value;
                 break;
             case TaskParameterKeys.HOLD_TIME:
-                this.setHoldTime(value);
-                mutableDefaults.holdTime = this.holdTime;
+                value = this.validateInput(
+                    value,
+                    this.MIN_HOLD_TIME_CONSTRAINT,
+                    this.MAX_HOLD_TIME_CONSTRAINT,
+                );
+                this.diveParameters.hold_time = value;
                 break;
             case TaskParameterKeys.DRIFT_TIME:
-                this.setDriftTime(value);
-                mutableDefaults.driftTime = this.driftTime;
+                value = this.validateInput(
+                    value,
+                    this.MIN_DRIFT_TIME_CONSTRAINT,
+                    this.NO_CONSTRAINT,
+                );
+                this.driftParameters.drift_time = value;
                 break;
             case TaskParameterKeys.HEADING:
-                this.setHeading(value);
-                mutableDefaults.heading = this.heading;
+                value = this.validateInput(
+                    value,
+                    this.MIN_HEADING_CONSTRAINT,
+                    this.MAX_HEADING_CONSTRAINT,
+                );
+                this.constantHeadingParameters.constant_heading = value;
                 break;
             case TaskParameterKeys.CONSTANT_HEADING_TIME:
-                this.setConstantHeadingTime(value);
-                mutableDefaults.constantHeadingTime = this.constantHeadingTime;
+                value = this.validateInput(
+                    value,
+                    this.MIN_CONSTANT_HEADING_TIME_CONSTRAINT,
+                    this.NO_CONSTRAINT,
+                );
+                this.constantHeadingParameters.constant_heading_time = value;
                 break;
             case TaskParameterKeys.SPEED:
-                this.setSpeed(value);
-                mutableDefaults.speed = this.speed;
+                value = this.validateInput(
+                    value,
+                    this.MIN_SPEED_CONSTRAINT,
+                    this.MAX_SPEED_CONSTRAINT,
+                );
+                this.constantHeadingParameters.constant_heading_speed = value;
                 break;
         }
 
-        jaiaGlobal.setDefaultTaskParameters(mutableDefaults);
-    }
-
-    getMaxDepth() {
-        return this.maxDepth;
-    }
-
-    setMaxDepth(maxDepth: number) {
-        if (maxDepth > this.MAX_DEPTH) {
-            maxDepth = this.MAX_DEPTH;
-        }
-
-        if (maxDepth < this.MIN_DEPTH) {
-            maxDepth = this.MIN_DEPTH;
-        }
-
-        this.maxDepth = maxDepth;
-    }
-
-    getDepthInterval() {
-        return this.depthInterval;
-    }
-
-    setDepthInterval(depthInterval: number) {
-        if (depthInterval > this.MAX_DEPTH) {
-            depthInterval = this.MAX_DEPTH;
-        }
-
-        if (depthInterval < this.MIN_DEPTH) {
-            depthInterval = this.MIN_DEPTH;
-        }
-
-        this.depthInterval = depthInterval;
+        this.updateDefaultTaskParameters();
     }
 
-    getHoldTime() {
-        return this.holdTime;
-    }
-
-    setHoldTime(holdTime: number) {
-        if (holdTime > this.MAX_HOLD_TIME) {
-            holdTime = this.MAX_HOLD_TIME;
+    private validateInput(value: number, min: number, max: number) {
+        if (value > max && max !== this.NO_CONSTRAINT) {
+            return max;
         }
 
-        if (holdTime < this.MIN_HOLD_TIME) {
-            holdTime = this.MIN_HOLD_TIME;
+        if (value < min && min !== this.NO_CONSTRAINT) {
+            return min;
         }
 
-        this.holdTime = holdTime;
+        return value;
     }
 
-    getDriftTime() {
-        return this.driftTime;
+    private updateDefaultTaskParameters() {
path: src/web/data/tasks/task.ts
user: login: michael-jaia
body: Yes
created_at: 2025-05-20T15:13:46Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,10 @@
+/**
path: src/web/utils/input.ts
user: login: JRoybot
body: do you prefer keeping this as a standalone or do you think it could be combined with one function in `src/web/utils/conversions.ts` in some catchall like `src/web/utils/utils.ts`
created_at: 2025-05-20T17:41:03Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,10 @@
+/**
path: src/web/utils/input.ts
user: login: michael-jaia
body: I was on the fence, but I am leaning yes because I can see this file being a good location for other function that deal with rounding and validating input values.
created_at: 2025-05-20T17:57:06Z
author_association: COLLABORATOR

PR comments:
user: login: JRoybot
created_at: 2025-05-13T13:28:56Z
body: Assume we will be adding all the existing options like bottom dive toggle, position on map, constant heading on map.... even if they are not implemented does it make sense to add those items to the panel now?
author_association: COLLABORATOR
user: login: michael-jaia
created_at: 2025-05-13T14:01:27Z
body: > Assume we will be adding all the existing options like bottom dive toggle, position on map, constant heading on map.... even if they are not implemented does it make sense to add those items to the panel now?

We will be adding those options back, but to prevent this PR from growing even larger, I would prefer to add those in future work.
author_association: COLLABORATOR