Skip to content

Commit 2bc9917

Browse files
committed
dev improvements
1 parent 92e7202 commit 2bc9917

9 files changed

Lines changed: 76 additions & 38 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Request and runtime flow:
2020
- Ensures DB exists and is migrated/seeded.
2121
- Opens DB connection pool.
2222
- Builds LTI data provider.
23-
- Starts Tailwind watcher in `Dev`.
23+
- Builds app context for controllers/views.
2424
3. `src/lti_example_tool_web/router.gleam` dispatches requests by path segments.
2525
4. `src/lti_example_tool_web/web.gleam` applies middleware (logging, crash rescue, static files, HEAD handling).
2626
5. Controllers render HTML with Nakai (`src/lti_example_tool_web/html*`) or JSON for JWKS.
@@ -64,7 +64,7 @@ Design style:
6464
3. `gleam deps download`
6565
4. `cp seeds.example.yml seeds.yml` (edit values)
6666
5. `gleam run -m lti_example_tool/database/migrate setup`
67-
6. `watchexec --stop-signal=SIGKILL -r -e gleam gleam run`
67+
6. `npm run dev`
6868
7. Open `http://localhost:8080`
6969

7070
### Docker Setup
@@ -117,7 +117,8 @@ Patterns to preserve:
117117
## Common Development Tasks
118118

119119
- Run app: `gleam run`
120-
- Run app with local auto-reload: `watchexec --stop-signal=SIGKILL -r -e gleam gleam run`
120+
- Run full local dev stack (server reload + Tailwind watch + client rebuild watch): `npm run dev`
121+
- Run server-only auto-reload: `npm run server:dev`
121122
- Build app: `gleam build`
122123
- Build CSS once: `npm run tailwind:build`
123124
- Watch CSS: `npm run tailwind:watch`

README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ Interoperability) specification.
9696
```sh
9797
cp seeds.example.yml seeds.yml
9898
```
99-
7. Run the server and watch for changes:
99+
7. Start development mode (server reload, Tailwind watch, and client HMR):
100100
```sh
101-
watchexec --stop-signal=SIGKILL -r -e gleam gleam run
101+
npm run dev
102102
```
103103
8. Open the application in your browser:
104104
```sh
@@ -111,8 +111,8 @@ Interoperability) specification.
111111
# Run the project
112112
gleam run
113113

114-
# Run the project and watch for changes
115-
watchexec --stop-signal=SIGKILL -r -e gleam gleam run
114+
# Run the full dev stack (server reload + tailwind watch + client rebuild watch)
115+
npm run dev
116116

117117
# Reset the database
118118
gleam run -m lti_example_tool/database/migrate reset
@@ -123,3 +123,16 @@ gleam run -m lti_example_tool/database/migrate up
123123
# Run the tests
124124
gleam test
125125
```
126+
127+
For LMS launches over ngrok, expose the app server:
128+
129+
Example:
130+
131+
```sh
132+
# terminal 1: start ngrok for the app server
133+
ngrok http 8080
134+
135+
# terminal 2: run dev
136+
export PUBLIC_URL="https://<app-tunnel>.ngrok.app"
137+
npm run dev
138+
```

client/src/App.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,14 @@ export function App() {
9999
}
100100

101101
function Detail({ label, value }: { label: string; value: string }) {
102+
const trimmed = value.trim();
103+
102104
return (
103105
<div className="grid gap-1 border-b border-gray-100 pb-3 sm:grid-cols-[9rem_1fr] sm:gap-3">
104106
<dt className="font-medium text-gray-700">{label}</dt>
105-
<dd className="break-all text-gray-900">{value.length > 0 ? value : "-"}</dd>
107+
<dd className="break-all text-gray-900">
108+
{trimmed.length > 0 ? trimmed : "None"}
109+
</dd>
106110
</div>
107111
);
108112
}

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
"tailwind:build": "npx tailwindcss -i ./app.css -o priv/static/app.css",
2020
"tailwind:watch": "npx tailwindcss -i ./app.css -o priv/static/app.css --watch",
2121
"client:build": "vite build --config client/vite.config.ts",
22-
"client:dev": "vite --config client/vite.config.ts",
22+
"client:watch": "vite build --watch --config client/vite.config.ts",
23+
"server:dev": "watchexec --stop-signal=SIGKILL -r -e gleam,erl,hrl -w src -w test -w gleam.toml -w manifest.toml gleam run",
24+
"dev": "bash ./scripts/dev.sh",
2325
"clean": "rm -rf build priv/static/**",
2426
"build": "gleam build && npm run tailwind:build && npm run client:build"
2527
}

scripts/dev.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
export ENV="${ENV:-dev}"
5+
6+
cleanup() {
7+
local pids
8+
pids="$(jobs -pr || true)"
9+
if [[ -n "$pids" ]]; then
10+
kill $pids >/dev/null 2>&1 || true
11+
fi
12+
}
13+
trap cleanup EXIT INT TERM
14+
15+
npm run tailwind:watch &
16+
npm run client:watch &
17+
18+
npm run server:dev

src/lti_example_tool/application.gleam

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ import lti_example_tool/config
77
import lti_example_tool/database
88
import lti_example_tool/database/migrate
99
import lti_example_tool/db_provider
10-
import lti_example_tool/env.{Dev}
1110
import lti_example_tool/feature_flags
12-
import lti_example_tool/utils/devtools
1311
import pog
1412
import wisp
1513

@@ -30,8 +28,6 @@ pub fn setup() -> AppContext {
3028

3129
let assert Ok(lti_data_provider) = db_provider.data_provider(db)
3230

33-
env.exec(env, Dev, fn() { devtools.start() })
34-
3531
let http_provider = httpc_provider.http_provider()
3632

3733
let feature_flags = feature_flags.load()

src/lti_example_tool/utils/devtools.gleam

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/lti_example_tool_web/controllers/lti_controller.gleam

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,9 @@ pub fn token(req: Request, app: AppContext) -> Response {
227227
}
228228
}
229229

230-
pub fn app(req: Request, _app: AppContext) -> Response {
230+
pub fn app(req: Request, app: AppContext) -> Response {
231231
use <- wisp.require_method(req, http.Get)
232-
render_html(lti_html.client_app())
232+
render_html(lti_html.client_app(app))
233233
}
234234

235235
fn unauthorized_response() -> Response {
@@ -371,8 +371,8 @@ fn launch_session_from_claims(
371371
use sub <- result.try(required_string_claim(claims, "sub"))
372372
use issuer <- result.try(required_string_claim(claims, "iss"))
373373
use audience <- result.try(required_audience_claim(claims))
374-
let name = optional_string_claim(claims, "name") |> result.unwrap(sub)
375374
let email = optional_string_claim(claims, "email") |> result.unwrap("")
375+
let name = preferred_name_from_claims(claims)
376376
let roles = optional_roles_claim(claims)
377377
let context_title = optional_context_title_claim(claims)
378378

@@ -387,6 +387,31 @@ fn launch_session_from_claims(
387387
))
388388
}
389389

390+
fn preferred_name_from_claims(claims: Dict(String, Dynamic)) -> String {
391+
let name = optional_string_claim(claims, "name") |> result.unwrap("")
392+
let given_name =
393+
optional_string_claim(claims, "given_name") |> result.unwrap("")
394+
let family_name =
395+
optional_string_claim(claims, "family_name") |> result.unwrap("")
396+
let combined_name = string.trim(given_name <> " " <> family_name)
397+
398+
first_non_empty([name, combined_name])
399+
}
400+
401+
fn first_non_empty(values: List(String)) -> String {
402+
case values {
403+
[value, ..rest] -> {
404+
let trimmed = string.trim(value)
405+
406+
case trimmed == "" {
407+
True -> first_non_empty(rest)
408+
False -> trimmed
409+
}
410+
}
411+
[] -> ""
412+
}
413+
}
414+
390415
fn required_string_claim(
391416
claims: Dict(String, Dynamic),
392417
key: String,

src/lti_example_tool_web/html/lti_html.gleam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import lti_example_tool_web/html/components/tables.{Column}
1818
import nakai/attr.{action, class, href, id, method, name, src, type_, value}
1919
import nakai/html.{type Node, div, form, h2, i, img, input, section, span}
2020

21-
pub fn client_app() -> Node {
21+
pub fn client_app(_app: AppContext) -> Node {
2222
page("Launch Successful", [
2323
div([class("mx-auto max-w-3xl w-full px-4 py-6")], [
2424
div([id("root")], []),

0 commit comments

Comments
 (0)