Skip to content

Commit 42830c4

Browse files
authored
docs: ship the DMS migration as a design case study (penpot-ready) (#3)
* docs: ship the DMS migration as a design case study (penpot-ready) A full Before / After / Roadmap walk through every dms surface we touched in ledoent/dms#1, packaged so a designer can import it into the design.hz.ledoweb.com Penpot instance without rebuilding from blank. ## What's in the bundle - `docs/case-studies/dms/README.md` — the master document. Reads as a design-review brief: aesthetic direction, surface-by-surface before/ after with embedded GCS-hosted screenshots, "why" + "next" for each, a 10-item ranked roadmap, and the token contract that ties the whole thing back to the design system. - `docs/case-studies/dms/surfaces.json` — structured frame manifest. One entry per Penpot page: label, screenshot URL, annotation text, forward-move list. The bootstrap script consumes this. - `docs/case-studies/dms/roadmap.json` — the 10-item roadmap as data (rank, surface, impact, effort, summary, dependency). Same content as the markdown table but consumable by the bootstrap script for the Roadmap page card stack. - `docs/case-studies/dms/scripts/bootstrap-penpot.mjs` — Node script that hits Penpot's REST API to create the project + file + one page per surface from `surfaces.json`, plus a Roadmap page from `roadmap.json`. Designer takes over from the skeleton. - `docs/case-studies/dms/scripts/README.md` — how to get a Penpot access token, run the script, and what it doesn't try to do. ## Why semi-automated for now `infra/deployments/penpot/README.md` calls out that the Penpot MCP server isn't wired into Claude yet. Until it is, the bootstrap script provides the REST-API floor: it creates the project skeleton and surfaces the screenshot URLs the designer drags onto each page. When MCP lands this script gets replaced by a declarative translator that is idempotent and layout-aware. ## Why a case study This is also the first artifact in a `docs/case-studies/` series. The same shape (README + surfaces.json + roadmap.json + bootstrap) should work for every OCA module we migrate to a coherent visual layer — establish the pattern here, reuse for the next module. * docs(dms): add idempotent Penpot service-account bootstrap script Registers hello@ledoweb.com on design.hz.ledoweb.com via the REST API, activates the profile via direct DB UPDATE (SMTP unconfigured on the cluster so verification email goes nowhere), mints a long-lived access token, and writes credentials to .env at the repo root. Re-runs reuse the existing password to refresh the token without locking out the account. Unblocks the Penpot MCP server wiring — the access token this writes is what design.hz.ledoweb.com/mcp authenticates against.
1 parent 3e25b68 commit 42830c4

7 files changed

Lines changed: 916 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ __pycache__/
33
.tmp-screens/
44
.DS_Store
55
node_modules/
6+
.env

docs/case-studies/dms/README.md

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# DMS — Case study
2+
3+
> A field guide to migrating the OCA `dms` module from 18.0 to 19.0 and giving it
4+
> a coherent visual layer. Before, after, why, and what's still on the table.
5+
6+
| | |
7+
| --- | --- |
8+
| **Module** | `dms` (Document Management System) |
9+
| **PR** | [ledoent/dms#1](https://github.com/ledoent/dms/pull/1), based on [OCA/dms#475](https://github.com/OCA/dms/pull/475) |
10+
| **Surfaces touched** | Directory kanban · File kanban · Form hero · Portal grid · Searchpanel · Drop-zone |
11+
| **Aesthetic direction** | "Field guide for documents" — editorial, data-density, archival |
12+
| **Single-variable contract** | `--ods-accent` |
13+
| **Asset bundle delta** | dms: −400 lines net after adopting `odoo_design_system` |
14+
15+
This is the case study companion for the design-system Penpot project at
16+
[`design.hz.ledoweb.com`](https://design.hz.ledoweb.com). Each surface below
17+
is documented with its before/after state, the rationale, and the next
18+
move. Surfaces map 1:1 onto Penpot frames; see
19+
[`surfaces.json`](./surfaces.json) for the manifest the
20+
[bootstrap script](./scripts/) reads when seeding a fresh Penpot project.
21+
22+
---
23+
24+
## Aesthetic direction
25+
26+
The migration committed to **"Field guide for documents"** — an editorial /
27+
archival voice rather than Odoo's default purple chrome.
28+
29+
| Axis | Choice | Rationale |
30+
| --- | --- | --- |
31+
| Tone | Editorial, technical, archival | Documents are evidence. Treat them like museum specimens. |
32+
| Type | System sans body + monospace for technical metadata | Tabular numerals for sizes, FA glyphs for chips. |
33+
| Color | One hash-keyed accent per identity (`--ods-accent`) | Per-file-extension for files, per-name-hash for directories. |
34+
| Density | Tight body, generous chip-row | Lets a user scan a long file list quickly. |
35+
| Motion | One micro-move on hover (1px lift + 4px shadow) | Earned attention; not jittery. |
36+
| Chrome | 3px accent spine + soft tile gradient | Reads as a "container," not a chip. |
37+
38+
---
39+
40+
## Surfaces
41+
42+
### 1. Directory kanban
43+
44+
The 18.0 OCA `dms` directory kanban has a generic folder icon, a name, tags,
45+
and a footer with text-and-icon counts. No tinting, no hover, footer markup
46+
renders `"5Directories"` without spacing because the ` ` token was a
47+
literal in the original markup.
48+
49+
**Before** — the migration carried the OCA 18.0 design forward without
50+
visual changes:
51+
52+
![Directory kanban — before](https://storage.googleapis.com/ledo-pr-assets/dms/pr-1/before-directories-kanban.png)
53+
54+
**After**`o_dms_card o_dms_dir_card` chrome with a 56 px `o_dms_card_tile`
55+
on the left, name + tags + relative time in the body, and a footer chip row
56+
(directory count, file count, human size, last-writer initials):
57+
58+
![Directory kanban — after](https://storage.googleapis.com/ledo-pr-assets/dms/pr-1/after-directories-kanban.png)
59+
60+
**Why**
61+
- The directory was the lowest-information card in the previous design.
62+
Reusing the file-kanban chip vocabulary for directory metadata makes the
63+
two views read as siblings.
64+
- Hash-tinted spine + tile (keyed off `data-initial="<first letter of
65+
name>"`) gives each directory a stable, distinct identity without per-
66+
record color picking.
67+
- The footer-chip pattern surfaces the same metadata that previously
68+
required a hover or a form-view trip.
69+
70+
**Next**
71+
- The folder glyph is still the upstream `dms/static/icons/folder.svg`;
72+
consider an `OdsCardTile` accent-`initial` variant that renders a styled
73+
monogram instead of an icon when the directory has no children. Lower
74+
density, more identity.
75+
- Color-picker integration with `--ods-accent` so a user-set kanban color
76+
overrides the hash-bucket tint.
77+
78+
---
79+
80+
### 2. File kanban
81+
82+
`[19.0][MIG]` brought over the OCA file kanban with its image-thumbnail-or-
83+
icon pattern. Our `[19.0][IMP]` pass layered the unified card chrome on top:
84+
spine, hover lift, tinted extension chip via `data-ext`, monospaced
85+
extension label, locked-by-me success chip.
86+
87+
![File kanban — after](https://storage.googleapis.com/ledo-pr-assets/dms/pr-1/after-files-kanban.png)
88+
89+
**Why**
90+
- Mimetype + extension is what users sort by; surfacing them as the
91+
card's accent makes scanning a 500-file directory tractable.
92+
- Image previews drop the chrome treatment (`o_dms_card_tile_preview`) so
93+
photos read as photos, not as decorated tiles.
94+
- The extension chip strips its leading dot (`PDF`, not `.PDF`) — small,
95+
but kept consistent across kanban + form + portal.
96+
97+
**Next**
98+
- Drag-to-reorder (currently kanban groups by directory only — no manual
99+
reordering within a group).
100+
- Multi-select chip strip at the top of the kanban when ≥1 record is
101+
checked: aggregate size + extension breakdown + bulk-action drop-down.
102+
Mirrors the file-list bulk-actions UX inside the kanban for touch users.
103+
- "Recently opened" smart-filter pinned to the searchpanel.
104+
105+
---
106+
107+
### 3. Form-view hero (directory + file)
108+
109+
Before: the form view dropped the user into a chrome-less header with the
110+
name as an `<h1>` and a `<notebook>` carrying everything else. To see the
111+
size, you had to expand the **Subdirectories** + **Files** tabs and count.
112+
113+
![Directory form — before](https://storage.googleapis.com/ledo-pr-assets/dms/pr-1/before-directory-form.png)
114+
115+
After: an `o_dms_dir_hero` block at the top with a tinted 28×28 hero icon
116+
(`o_dms_hero_icon`), the name, a metadata pill row (size, root-pill,
117+
elements count), and the path breadcrumb. No tab-clicking required to read
118+
the essentials.
119+
120+
![Directory form — after](https://storage.googleapis.com/ledo-pr-assets/dms/pr-1/after-directory-form.png)
121+
122+
**Why**
123+
- The form hero answers the four questions a user asks 90 % of the time:
124+
what is this, how big, what's inside, where is it? Surface them.
125+
- Directory hero pins `--ods-accent` to amber (`#f08c00`), matching the
126+
default kanban tile so navigating kanban → form keeps a visual
127+
through-line.
128+
- File hero stays neutral gray because per-extension tinting requires
129+
evaluating `record.extension.raw_value` in the form arch, which Odoo's
130+
static XML can't do — out-of-scope for this PR.
131+
132+
**Next**
133+
- Make the file hero per-extension-tinted via a tiny field widget that
134+
renders the `<OdsCardTile>` component (OWL can read the record's
135+
extension field at runtime).
136+
- Inline "share" action in the hero (currently buried in the Actions
137+
menu).
138+
- Versioning chip when `dms_version` add-on is installed.
139+
140+
---
141+
142+
### 4. Portal `/my/dms`
143+
144+
The 18.0 portal rendered the docs as a flat `<ul>` of links — accurate but
145+
flat. The new portal grid lays each directory and file as a card with a
146+
tinted left stripe (per-extension for files, amber for directories), a
147+
soft top-edge gradient, and a hover lift identical to the backend kanban.
148+
Extension shows as a monospaced badge.
149+
150+
![Portal — directory grid](https://storage.googleapis.com/ledo-pr-assets/dms/pr-1/after-portal-grid.png)
151+
152+
![Portal — mixed grid](https://storage.googleapis.com/ledo-pr-assets/dms/pr-1/after-portal-mixed.png)
153+
154+
**Why**
155+
- Portal users are typically less technical than backend users — visual
156+
hierarchy matters more. Cards beat list items for scannability.
157+
- Same accent system as backend means a customer-portal user sees the
158+
same color for a PDF that an internal user sees in the kanban. Reduces
159+
cognitive load when the two surfaces are referenced side by side
160+
("see the red one in your portal").
161+
- Top-edge gradient is `color-mix(in srgb, var(--ods-accent) 6%, white)`
162+
— derived, not declared. Adding a new extension tints the gradient
163+
automatically.
164+
165+
**Next**
166+
- Mobile portrait: drop the side-by-side info; stack the badge above the
167+
body and tighten line-height.
168+
- Empty-state illustration with a Lucide icon at scale instead of the
169+
current `alert-warning` "Not results" message.
170+
- Inline preview-on-hover for image/PDF files (the desktop pattern; mobile
171+
taps through as before).
172+
173+
---
174+
175+
### 5. Searchpanel
176+
177+
Compact monospaced section headers, dimmed counters, tabular numerals so
178+
column-of-counts align. Functional but understated.
179+
180+
**Why**
181+
- Searchpanel labels are repeated UI furniture; a smaller, monospaced
182+
treatment gets out of the way so the items themselves dominate.
183+
- Tabular numerals matter on long lists where counts vary from 1 to 999.
184+
185+
**Next**
186+
- "Recently opened" smart filter (would also serve File Kanban — see
187+
surface 2 roadmap).
188+
- Saved-search chips persisted per-user (currently lost on logout).
189+
- Hierarchy collapse-state persisted (Odoo loses it across navigation).
190+
191+
---
192+
193+
### 6. Drop-zone overlay
194+
195+
`o_dms_dropzone` — animated marching-dashed border + bobbing cloud-upload
196+
icon, fires when a file is dragged onto the file kanban.
197+
198+
**Why**
199+
- File ingestion is the module's hot path. Making the drop target
200+
unambiguous reduces the "where do I drop?" fumble.
201+
- Marching dashes + bob = motion that **describes the action** (this is
202+
an active target) rather than decorative.
203+
204+
**Next**
205+
- Drop-on-directory-card to upload directly into that directory (today's
206+
drop-zone is module-wide; the directory under the cursor is ignored).
207+
- File-type-aware overlay copy: "drop 3 files (2 images, 1 PDF)" once a
208+
drag enters the page; uses the dragged FileList preview.
209+
- Progress feedback during upload (currently silent; the file just
210+
appears).
211+
212+
---
213+
214+
## Roadmap
215+
216+
Concrete, ranked. Each item lists the surface it touches, the impact, the
217+
effort, and any cross-dependency.
218+
219+
| # | Item | Surface | Impact | Effort | Dependency |
220+
| --- | --- | --- | --- | --- | --- |
221+
| 1 | **Per-extension tinted file form hero** (replace static `o_dms_hero_icon` with `<OdsCardTile>` that reads `record.extension`) | Form hero (file) | Medium | Small (~½ day) | `odoo_design_system` (already merged on `19.0`) |
222+
| 2 | **Drop-on-directory-card** to upload into the targeted directory | Drop-zone + directory kanban | High | Medium (~1–2 days) | Existing `dms_file_upload.esm.js` extension |
223+
| 3 | **Multi-select chip strip** with bulk actions on the file kanban | File kanban | High | Medium (~2 days) | Odoo 19 kanban multi-select API |
224+
| 4 | **Mobile portrait portal**: stack badge + tighten leading | Portal | Medium | Small (~½ day) | Pure SCSS pass |
225+
| 5 | **Empty-state illustration** for `/my/dms` "no results" | Portal | Medium | Small (~½ day) | unDraw / Storyset CC0 asset import |
226+
| 6 | **Versioning chip** in the file hero when `dms_version` is installed | Form hero (file) | Medium | Small (~1 day) | Optional `dms_version` addon |
227+
| 7 | **Saved-search chips** persisted per user across sessions | Searchpanel | Medium | Medium (~1 day) | `ir.filters` polish |
228+
| 8 | **Drag-to-reorder** files within a kanban group | File kanban | Low | Medium (~1–2 days) | Manual-sort field on `dms.file` |
229+
| 9 | **Inline preview-on-hover** for image/PDF on the portal | Portal | Medium | Medium (~2 days) | `web/static/lib/pdfjs` reuse |
230+
| 10 | **`OdsTokens` JS export** so OWL code can read the bucket palette without re-declaring | Cross-cutting | Low | Small (~½ day) | Pure addition to `odoo_design_system` |
231+
232+
**Ranking principle.** Items 1 and 2 are highest impact for the same effort
233+
because they close a known visual+functional gap with the rest of the
234+
module. Item 5 is the "no extra cost" win — purely a swap of one alert
235+
banner for one CC0 illustration.
236+
237+
---
238+
239+
## Token contract (single source of truth)
240+
241+
Every visual in this case study reads from the seven tokens declared in
242+
`design.hz.ledoweb.com`**Ledo Design System****Tokens**`color/`
243+
and `size/`:
244+
245+
| Token | Value | Where it lives in code |
246+
| --- | --- | --- |
247+
| `color.brand.primary` | `#D97706` | `$o-brand-primary` (Sass) → `--o-brand-primary` (CSS) |
248+
| `color.brand.primary-hover` | `#B45309` | `--o-brand-primary-hover` |
249+
| `color.brand.primary-active` | `#92400E` | `--o-brand-primary-active` |
250+
| `color.brand.secondary` | `#F59E0B` | `--o-brand-secondary` |
251+
| `color.bucket.{1..8}` | 8 hash-keyed tints | `$ods-bucket-palette` |
252+
| `color.ext.{family}` | per-file-family accents | `$ods-extension-accents` |
253+
| `size.tile` | `56px` | `--ods-tile-size` |
254+
| `size.avatar` | `22px` | `--ods-avatar-size` |
255+
| `size.spine` | `3px` | `--ods-spine-width` |
256+
257+
The chain — DTCG JSON in Penpot → `pnpm run tokens``_tokens.generated.scss`
258+
→ Odoo `web.assets_*` bundle — is documented in
259+
[`odoo_design_system/readme/USAGE.md`](../../../odoo_design_system/readme/USAGE.md).
260+
PR [#2](https://github.com/ledoent/odoo-design-system/pull/2) wires
261+
style-dictionary. PR [!48 on `ledoent/erp`](https://gitlab.com/ledoent/erp/-/merge_requests/48)
262+
closes the chain through to the brand logo's inline-SVG fills.
263+
264+
---
265+
266+
## Penpot setup
267+
268+
The Penpot project this case study describes maps to one project, one
269+
file, and one page per major surface:
270+
271+
```
272+
Project "Ledo Design System"
273+
└── File "DMS — Migration & Modernization"
274+
├── Page "1 · Directory Kanban" — before / after / annotations / next
275+
├── Page "2 · File Kanban"
276+
├── Page "3 · Form Hero"
277+
├── Page "4 · Portal Grid"
278+
├── Page "5 · Searchpanel"
279+
├── Page "6 · Drop-zone"
280+
└── Page "7 · Roadmap"
281+
```
282+
283+
The frame manifest lives in [`surfaces.json`](./surfaces.json) — each entry
284+
gives a frame name, the GCS-hosted image URLs to embed, and the annotation
285+
text from the corresponding section above.
286+
287+
The roadmap data lives in [`roadmap.json`](./roadmap.json) — keep the
288+
markdown table above and that JSON file in sync; the same items render in
289+
the Penpot "7 · Roadmap" page as a card stack ranked by impact ÷ effort.
290+
291+
### Seeding the project
292+
293+
Until the Penpot MCP server is wired into Claude (see
294+
[`infra/deployments/penpot/README.md`](https://gitlab.com/ledoent/infra/-/tree/main/deployments/penpot)),
295+
project creation is semi-automated:
296+
297+
```bash
298+
# Get a Penpot access token: design.hz.ledoweb.com → Profile → Access tokens
299+
export PENPOT_TOKEN=…
300+
301+
# Create the project + file + pages, upload all screenshots as media,
302+
# and lay out one frame per surface with its annotation text.
303+
node docs/case-studies/dms/scripts/bootstrap-penpot.mjs
304+
```
305+
306+
The script is documented in
307+
[`scripts/README.md`](./scripts/README.md). It creates the skeleton;
308+
final layout polish stays a designer task.

0 commit comments

Comments
 (0)