WW-5416 fix: restore default struts.action.extension in examples (#541)
* WW-5416 docs: design for action.extension 404 fix
Design doc for restoring the framework-default struts.action.extension
in helloworld, text-provider, and sitemesh3 so documented .action URLs
no longer return 404.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-5416 docs: implementation plan for action.extension 404 fix
Task-by-task plan to remove the struts.action.extension override in
helloworld, text-provider, and sitemesh3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-5416 fix(helloworld): restore default struts.action.extension
Removing the ',' override restores the framework default 'action,,' so
the documented .action URLs return 200 instead of 404.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-5416 fix(text-provider): restore default struts.action.extension
Removing the ',' override restores the framework default 'action,,' so
.action URLs no longer 404.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-5416 fix(sitemesh3): restore default struts.action.extension
Removing the empty-string override restores the framework default
'action,,' so .action URLs no longer 404.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
diff --git a/docs/superpowers/plans/2026-07-23-helloworld-action-extension-404.md b/docs/superpowers/plans/2026-07-23-helloworld-action-extension-404.md
new file mode 100644
index 0000000..6ccb427
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-23-helloworld-action-extension-404.md
@@ -0,0 +1,264 @@
+# WW-5416 action.extension 404 Fix — Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Restore the framework-default `struts.action.extension` in the `helloworld`, `text-provider`, and `sitemesh3` example modules so documented `.action` URLs return 200 instead of 404.
+
+**Architecture:** Each affected module overrides `struts.action.extension` to a value that excludes the `.action` extension. Deleting that one `<constant>` line restores the framework default `action,,`, which accepts both `.action` and extensionless URLs. In-app navigation already routes through Struts URL/form tags, so widening the allowed extension set is strictly additive — no existing route breaks.
+
+**Tech Stack:** Struts 2 (XML config), Maven multi-module build, Jetty Maven plugin for local run.
+
+## Global Constraints
+
+- Do **not** modify `rest-angular` (`struts.action.extension=,,xml,json,action`) or `mailreader2` (`struts.action.extension=do`) — those overrides are intentional.
+- No new automated/integration test is added (per spec scope decision).
+- Commit messages use the repo convention: `WW-5416 fix(<module>): <description>` (JIRA ticket prefix required).
+- Work happens on branch `fix/WW-5416-action-extension-404` (already created).
+- Each task must keep `mvn -pl <module> clean package` green.
+
+---
+
+### Task 1: Fix `helloworld` (the ticket)
+
+**Files:**
+- Modify: `helloworld/src/main/resources/struts.xml:9`
+
+**Interfaces:**
+- Consumes: nothing from other tasks.
+- Produces: nothing other tasks depend on (each task is independent).
+
+- [ ] **Step 1: Confirm the bug exists (baseline)**
+
+Start the app in the background and confirm the documented URL 404s:
+
+```bash
+cd /Users/lukaszlenart/Projects/Apache/struts-examples/helloworld
+mvn -q jetty:run &
+JETTY_PID=$!
+# wait for startup
+until curl -s -o /dev/null http://localhost:8080/helloworld/ ; do sleep 2; done
+curl -s -o /dev/null -w "index.action -> %{http_code}\n" http://localhost:8080/helloworld/index.action
+kill $JETTY_PID
+```
+
+Expected (before fix): `index.action -> 404`
+
+- [ ] **Step 2: Delete the extension override**
+
+In `helloworld/src/main/resources/struts.xml`, remove this line (line 9):
+
+```xml
+ <constant name="struts.action.extension" value=","/>
+```
+
+Leave `struts.devMode` and `struts.allowlist.packageNames` constants untouched.
+
+- [ ] **Step 3: Verify the build still passes**
+
+Run: `mvn -q -pl helloworld clean package`
+Expected: `BUILD SUCCESS`
+
+- [ ] **Step 4: Verify the documented URL now returns 200**
+
+```bash
+cd /Users/lukaszlenart/Projects/Apache/struts-examples/helloworld
+mvn -q jetty:run &
+JETTY_PID=$!
+until curl -s -o /dev/null http://localhost:8080/helloworld/ ; do sleep 2; done
+curl -s -o /dev/null -w "index.action -> %{http_code}\n" http://localhost:8080/helloworld/index.action
+curl -s -o /dev/null -w "hello.action -> %{http_code}\n" http://localhost:8080/helloworld/hello.action
+curl -s -o /dev/null -w "hello (no ext) -> %{http_code}\n" http://localhost:8080/helloworld/hello
+kill $JETTY_PID
+```
+
+Expected (after fix):
+```
+index.action -> 200
+hello.action -> 200
+hello (no ext) -> 200
+```
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add helloworld/src/main/resources/struts.xml
+git commit -m "WW-5416 fix(helloworld): restore default struts.action.extension
+
+Removing the ',' override restores the framework default 'action,,' so
+the documented .action URLs return 200 instead of 404.
+
+Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
+```
+
+---
+
+### Task 2: Fix `text-provider`
+
+**Files:**
+- Modify: `text-provider/src/main/resources/struts.xml:8`
+
+**Interfaces:**
+- Consumes: nothing from other tasks.
+- Produces: nothing other tasks depend on.
+
+- [ ] **Step 1: Confirm the bug exists (baseline)**
+
+```bash
+cd /Users/lukaszlenart/Projects/Apache/struts-examples/text-provider
+mvn -q jetty:run &
+JETTY_PID=$!
+until curl -s -o /dev/null http://localhost:8080/text-provider/ ; do sleep 2; done
+curl -s -o /dev/null -w "index.action -> %{http_code}\n" http://localhost:8080/text-provider/index.action
+kill $JETTY_PID
+```
+
+Expected (before fix): `index.action -> 404`
+
+- [ ] **Step 2: Delete the extension override**
+
+In `text-provider/src/main/resources/struts.xml`, remove this line (line 8):
+
+```xml
+ <constant name="struts.action.extension" value=","/>
+```
+
+Leave the `struts.convention.action.packages`, `struts.custom.i18n.resources`, and all `bean`/`TextProviderFactory` constants untouched.
+
+- [ ] **Step 3: Verify the build still passes**
+
+Run: `mvn -q -pl text-provider clean package`
+Expected: `BUILD SUCCESS`
+
+- [ ] **Step 4: Verify the documented URL now returns 200**
+
+```bash
+cd /Users/lukaszlenart/Projects/Apache/struts-examples/text-provider
+mvn -q jetty:run &
+JETTY_PID=$!
+until curl -s -o /dev/null http://localhost:8080/text-provider/ ; do sleep 2; done
+curl -s -o /dev/null -w "index.action -> %{http_code}\n" http://localhost:8080/text-provider/index.action
+curl -s -o /dev/null -w "index (no ext) -> %{http_code}\n" http://localhost:8080/text-provider/index
+kill $JETTY_PID
+```
+
+Expected (after fix):
+```
+index.action -> 200
+index (no ext) -> 200
+```
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add text-provider/src/main/resources/struts.xml
+git commit -m "WW-5416 fix(text-provider): restore default struts.action.extension
+
+Removing the ',' override restores the framework default 'action,,' so
+.action URLs no longer 404.
+
+Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
+```
+
+---
+
+### Task 3: Fix `sitemesh3`
+
+**Files:**
+- Modify: `sitemesh3/src/main/resources/struts.xml:8`
+
+**Interfaces:**
+- Consumes: nothing from other tasks.
+- Produces: nothing other tasks depend on.
+
+- [ ] **Step 1: Confirm the bug exists (baseline)**
+
+```bash
+cd /Users/lukaszlenart/Projects/Apache/struts-examples/sitemesh3
+mvn -q jetty:run &
+JETTY_PID=$!
+until curl -s -o /dev/null http://localhost:8080/sitemesh3/ ; do sleep 2; done
+curl -s -o /dev/null -w "hello.action -> %{http_code}\n" http://localhost:8080/sitemesh3/hello.action
+kill $JETTY_PID
+```
+
+Expected (before fix): `hello.action -> 404`
+
+- [ ] **Step 2: Delete the extension override**
+
+In `sitemesh3/src/main/resources/struts.xml`, remove this line (line 8):
+
+```xml
+ <constant name="struts.action.extension" value=""/>
+```
+
+Leave `struts.devMode`, `struts.ui.theme`, and `struts.custom.i18n.resources` constants untouched.
+
+- [ ] **Step 3: Verify the build still passes**
+
+Run: `mvn -q -pl sitemesh3 clean package`
+Expected: `BUILD SUCCESS`
+
+- [ ] **Step 4: Verify the documented URL now returns 200 and SiteMesh decoration still applies**
+
+```bash
+cd /Users/lukaszlenart/Projects/Apache/struts-examples/sitemesh3
+mvn -q jetty:run &
+JETTY_PID=$!
+until curl -s -o /dev/null http://localhost:8080/sitemesh3/ ; do sleep 2; done
+curl -s -o /dev/null -w "hello.action -> %{http_code}\n" http://localhost:8080/sitemesh3/hello.action
+curl -s -o /dev/null -w "hello (no ext) -> %{http_code}\n" http://localhost:8080/sitemesh3/hello
+curl -s -o /dev/null -w "admin/hello.action -> %{http_code}\n" http://localhost:8080/sitemesh3/admin/hello.action
+kill $JETTY_PID
+```
+
+Expected (after fix):
+```
+hello.action -> 200
+hello (no ext) -> 200
+admin/hello.action -> 200
+```
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add sitemesh3/src/main/resources/struts.xml
+git commit -m "WW-5416 fix(sitemesh3): restore default struts.action.extension
+
+Removing the empty-string override restores the framework default
+'action,,' so .action URLs no longer 404.
+
+Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
+```
+
+---
+
+### Task 4: Full multi-module build verification
+
+**Files:**
+- None modified. Final gate across the whole repo.
+
+**Interfaces:**
+- Consumes: the three fixes from Tasks 1–3.
+- Produces: confirmation that the reactor build is green.
+
+- [ ] **Step 1: Build every module**
+
+Run:
+```bash
+cd /Users/lukaszlenart/Projects/Apache/struts-examples
+mvn -q clean package
+```
+Expected: `BUILD SUCCESS` with all modules reactored.
+
+- [ ] **Step 2: Confirm no unintended changes to out-of-scope modules**
+
+Run: `git diff main --stat`
+Expected: only these three files changed (plus the docs added earlier):
+```
+helloworld/src/main/resources/struts.xml
+text-provider/src/main/resources/struts.xml
+sitemesh3/src/main/resources/struts.xml
+```
+Confirm `rest-angular` and `mailreader2` are NOT listed.
+
+- [ ] **Step 3: (No commit)** — this task only verifies; nothing new to commit.
diff --git a/docs/superpowers/specs/2026-07-23-helloworld-action-extension-404-design.md b/docs/superpowers/specs/2026-07-23-helloworld-action-extension-404-design.md
new file mode 100644
index 0000000..4e0013e
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-23-helloworld-action-extension-404-design.md
@@ -0,0 +1,82 @@
+# WW-5416: Hello World example returns 404 — design
+
+- **JIRA:** [WW-5416](https://issues.apache.org/jira/browse/WW-5416)
+- **Fix version:** 7.3.0
+- **Date:** 2026-07-23
+
+## Problem
+
+Following the documented Hello World instructions produces a 404. The tutorial (and
+`helloworld/README.txt`) tell users to visit `http://localhost:8080/helloworld/index.action`,
+but the app returns 404 for any `.action` URL.
+
+### Root cause
+
+`helloworld/src/main/resources/struts.xml` sets:
+
+```xml
+<constant name="struts.action.extension" value=","/>
+```
+
+The framework default is `action,,`, which maps **both** `.action` URLs and extensionless
+URLs. Overriding it with `,` reduces the allowed set to the empty extension only, so
+`.action` URLs no longer resolve and 404.
+
+The examples' own pages navigate via Struts URL/form tags (e.g. `<s:url action="hello"/>`),
+which emit URLs using whatever extension is configured — so in-app navigation "works" and
+the bug is invisible until a user types the documented `.action` URL directly.
+
+### Same latent bug in other examples
+
+| Example | `struts.action.extension` | Verdict |
+|---|---|---|
+| `helloworld` | `","` | Bug — fix (the ticket) |
+| `text-provider` | `","` | Same latent bug — fix |
+| `sitemesh3` | `""` | Same latent bug — fix |
+| `rest-angular` | `",,xml,json,action"` | Intentional (REST/JSON demo) — leave |
+| `mailreader2` | `"do"` | Intentional (Struts1 `.do` migration) — leave |
+
+## Approach
+
+Delete the `struts.action.extension` override in the three affected modules, restoring the
+framework default `action,,`.
+
+**Rejected alternative:** setting the value explicitly to `action,,`. Same runtime effect but
+adds a config line that only restates the default — no teaching value for example apps.
+
+## Change set
+
+Three one-line deletions:
+
+- `helloworld/src/main/resources/struts.xml` — remove `<constant name="struts.action.extension" value=","/>`
+- `text-provider/src/main/resources/struts.xml` — remove `<constant name="struts.action.extension" value=","/>`
+- `sitemesh3/src/main/resources/struts.xml` — remove `<constant name="struts.action.extension" value=""/>`
+
+`rest-angular` and `mailreader2` are explicitly left unchanged.
+
+## Behavior after fix
+
+- `<s:url action="..."/>` tags emit `.action` URLs by default → in-app links match the tutorials.
+- Directly-typed documented URLs (`/helloworld/index.action`, `/helloworld/hello.action`) return 200.
+- Extensionless URLs (`/hello`) still resolve — the default is a superset, so nothing that
+ currently navigates breaks.
+
+## Risk
+
+Low / additive. No Java code in these modules inspects the URL extension; all navigation
+routes through Struts tags that respect the configured extension. Widening the allowed
+extension set cannot remove a previously working route.
+
+## Out of scope
+
+- The authoritative "Getting Started" tutorial text lives in the separate `struts-site`
+ repository; not edited here. `helloworld/README.txt` already references `.action` and
+ becomes correct once the override is removed.
+- No new automated/integration test (per scope decision). Regression is guarded only by the
+ existing full build.
+
+## Verification
+
+- `mvn clean package` succeeds for all modules.
+- For `helloworld`, `text-provider`, and `sitemesh3`: `mvn jetty:run`, then confirm the
+ documented `.action` URL returns 200 and the in-app links navigate correctly.
diff --git a/helloworld/src/main/resources/struts.xml b/helloworld/src/main/resources/struts.xml
index c7d9171..45529a3 100755
--- a/helloworld/src/main/resources/struts.xml
+++ b/helloworld/src/main/resources/struts.xml
@@ -6,7 +6,6 @@
<struts>
<constant name="struts.devMode" value="true"/>
- <constant name="struts.action.extension" value=","/>
<constant name="struts.allowlist.packageNames" value="org.apache.struts.helloworld.model"/>
<package name="basicstruts2" namespace="/" extends="struts-default">
diff --git a/sitemesh3/src/main/resources/struts.xml b/sitemesh3/src/main/resources/struts.xml
index 875a3db..97af649 100644
--- a/sitemesh3/src/main/resources/struts.xml
+++ b/sitemesh3/src/main/resources/struts.xml
@@ -5,7 +5,6 @@
<struts>
<constant name="struts.devMode" value="true"/>
<constant name="struts.ui.theme" value="simple"/>
- <constant name="struts.action.extension" value=""/>
<constant name="struts.custom.i18n.resources" value="DefaultMessages"/>
<package name="default" extends="struts-default" namespace="/">
diff --git a/text-provider/src/main/resources/struts.xml b/text-provider/src/main/resources/struts.xml
index 7d1e2b4..00cdb31 100644
--- a/text-provider/src/main/resources/struts.xml
+++ b/text-provider/src/main/resources/struts.xml
@@ -5,7 +5,6 @@
<struts>
<constant name="struts.devMode" value="true"/>
<constant name="struts.convention.action.packages" value="org.apache.struts_example"/>
- <constant name="struts.action.extension" value=","/>
<constant name="struts.custom.i18n.resources" value="DefaultMessages"/>