Replace the accordion-based "Edit Description" with inline edit/view mode swapping using HTMX partials.
Description
┌─────────────────────────────────┐
│ The task description text... │
└─────────────────────────────────┘
▶ Edit Description (accordion expands to show textarea)
View Mode:
Description Edit
┌─────────────────────────────────┐
│ The task description text... │
└─────────────────────────────────┘
Edit Mode (after clicking Edit):
Description Cancel
┌─────────────────────────────────┐
│ [textarea with current text] │
└─────────────────────────────────┘
[Save]
Add to API type:
:<|> "tasks" :> Capture "id" Text :> "description" :> "view" :> Get '[Lucid.HTML] DescriptionViewPartial
:<|> "tasks" :> Capture "id" Text :> "description" :> "edit" :> Get '[Lucid.HTML] DescriptionEditPartial
Modify existing POST to return the view partial instead of redirect:
:<|> "tasks" :> Capture "id" Text :> "description" :> ReqBody '[FormUrlEncoded] DescriptionForm :> Post '[Lucid.HTML] DescriptionViewPartial
data DescriptionViewPartial = DescriptionViewPartial Text Text Bool -- taskId, description, isEpic
data DescriptionEditPartial = DescriptionEditPartial Text Text Bool -- taskId, description, isEpic
instance Lucid.ToHtml DescriptionViewPartial where
toHtmlRaw = Lucid.toHtml
toHtml (DescriptionViewPartial tid desc isEpic) =
Lucid.div_ [Lucid.id_ "description-block", Lucid.class_ "description-block"] <| do
Lucid.div_ [Lucid.class_ "description-header"] <| do
Lucid.h3_ (if isEpic then "Design" else "Description")
Lucid.a_
[ Lucid.href_ "#",
Lucid.class_ "edit-link",
Lucid.makeAttribute "hx-get" ("/tasks/" <> tid <> "/description/edit"),
Lucid.makeAttribute "hx-target" "#description-block",
Lucid.makeAttribute "hx-swap" "outerHTML"
]
"Edit"
if Text.null desc
then Lucid.p_ [Lucid.class_ "empty-msg"] (if isEpic then "No design document yet." else "No description yet.")
else
if isEpic
then Lucid.div_ [Lucid.class_ "markdown-content"] (renderMarkdown desc)
else Lucid.pre_ [Lucid.class_ "description"] (Lucid.toHtml desc)
instance Lucid.ToHtml DescriptionEditPartial where
toHtmlRaw = Lucid.toHtml
toHtml (DescriptionEditPartial tid desc isEpic) =
Lucid.div_ [Lucid.id_ "description-block", Lucid.class_ "description-block editing"] <| do
Lucid.div_ [Lucid.class_ "description-header"] <| do
Lucid.h3_ (if isEpic then "Design" else "Description")
Lucid.a_
[ Lucid.href_ "#",
Lucid.class_ "cancel-link",
Lucid.makeAttribute "hx-get" ("/tasks/" <> tid <> "/description/view"),
Lucid.makeAttribute "hx-target" "#description-block",
Lucid.makeAttribute "hx-swap" "outerHTML",
Lucid.makeAttribute "hx-confirm" "Discard changes?"
]
"Cancel"
Lucid.form_
[ Lucid.makeAttribute "hx-post" ("/tasks/" <> tid <> "/description"),
Lucid.makeAttribute "hx-target" "#description-block",
Lucid.makeAttribute "hx-swap" "outerHTML"
]
<| do
Lucid.textarea_
[ Lucid.name_ "description",
Lucid.class_ "description-textarea",
Lucid.rows_ (if isEpic then "15" else "10"),
Lucid.placeholder_ (if isEpic then "Enter design in Markdown..." else "Enter description...")
]
(Lucid.toHtml desc)
Lucid.div_ [Lucid.class_ "form-actions"] <| do
Lucid.button_ [Lucid.type_ "submit", Lucid.class_ "btn btn-primary"] "Save"
descriptionViewHandler :: Text -> Servant.Handler DescriptionViewPartial
descriptionViewHandler tid = do
tasks <- liftIO TaskCore.loadTasks
case TaskCore.findTask tid tasks of
Nothing -> throwError err404
Just task -> pure (DescriptionViewPartial tid (TaskCore.taskDescription task) (TaskCore.taskType task == TaskCore.Epic))
descriptionEditHandler :: Text -> Servant.Handler DescriptionEditPartial
descriptionEditHandler tid = do
tasks <- liftIO TaskCore.loadTasks
case TaskCore.findTask tid tasks of
Nothing -> throwError err404
Just task -> pure (DescriptionEditPartial tid (TaskCore.taskDescription task) (TaskCore.taskType task == TaskCore.Epic))
descriptionPostHandler :: Text -> DescriptionForm -> Servant.Handler DescriptionViewPartial
descriptionPostHandler tid (DescriptionForm newDesc) = do
liftIO (TaskCore.updateTaskDescription tid newDesc)
tasks <- liftIO TaskCore.loadTasks
case TaskCore.findTask tid tasks of
Nothing -> throwError err404
Just task -> pure (DescriptionViewPartial tid (TaskCore.taskDescription task) (TaskCore.taskType task == TaskCore.Epic))
Replace the <details> accordion section with initial view partial:
-- In TaskDetailFound render, replace the description detail-section with:
Lucid.div_ [Lucid.class_ "detail-section"] <| do
Lucid.toHtml (DescriptionViewPartial (TaskCore.taskId task) (TaskCore.taskDescription task) (TaskCore.taskType task == TaskCore.Epic))
".description-block" ? do
-- container styles if needed
pure ()
".description-header" ? do
display flex
justifyContent spaceBetween
alignItems center
marginBottom (px 8)
(".description-header" |> "h3") ? do
margin (px 0) (px 0) (px 0) (px 0)
".edit-link" <> ".cancel-link" ? do
fontSize (px 12)
color "#0066cc"
".cancel-link" ? color "#dc2626" -- red for cancel
Dark mode:
".cancel-link" ? color "#f87171" -- red-400
1. Omni/Jr/Web.hs:
<details> accordion code2. Omni/Jr/Web/Style.hs:
.description-header styles.edit-link, .cancel-link styles1. View task with description - see "Edit" link 2. View task without description - see "Edit" link + "No description yet" 3. Click Edit - swaps to textarea with current content 4. Click Cancel without changes - shows "Discard changes?" then swaps back 5. Click Cancel after typing - shows "Discard changes?" then swaps back 6. Type new content, click Save - swaps to view with new content 7. Refresh page - changes persisted 8. Test with Epic (markdown rendered) vs WorkTask (pre text) 9. Check dark mode styling
No activity yet.