Skip to content

Commit bd2a14a

Browse files
nodrel-devnoctisreusclaude
authored
fix: cognitive-load UX polish — credential notices, country dropdown, dimension hints (#22)
- Add per-resource notices in the main node explaining which credential type each resource needs and why a mismatched credential shows a red indicator (Track ↔ Shipping separate-keys trap, ADR-0004) - Convert Country Code free-text to a dropdown (COUNTRY_OPTIONS, ~250 countries, default US) — removes ISO-3166 recall work for the common case - Repeat the all-or-nothing dimensions hint on Width and Height (was only on Length) - Add description to Label Stock Type field - Add cost-notice to Create: surfaces real-money / sandbox-free fact up front - Mark both cognitive-load audit files Status: Fixed Co-authored-by: Kyle Tully <kytully@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 53fba7e commit bd2a14a

6 files changed

Lines changed: 311 additions & 6 deletions

File tree

docs/audits/cognitive-load-credential-setup-20260619-191805-1f36.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Cognitive-Load Audit — Credential setup screen
22

3+
> **Status: Fixed — 2026-06-20T00:00:00Z**
4+
> All 4 priority recommendations implemented: notice relabels Client ID/Secret to API Key/Secret Key (#1), notice flags project separation and separate-keys requirement (#2), `#credentials` README anchor verified with full walkthrough (#3), Environment description tightened (#4).
5+
>
36
> Run 2026-06-19. Target = the n8n credential config for `FedexTrackOAuth2Api` /
47
> `FedexShippingOAuth2Api`. This is the first-run gate: a user who can't configure a
58
> credential never reaches a single operation.

docs/audits/cognitive-load-operation-ui-20260619-191805-1f35.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
# Cognitive-Load / Conversion Audit — FedEx node configuration UI
22

3-
> **Implementation status (2026-06-19):** Recommendations #1, #2, #3 are **shipped**.
3+
> **Status: Fixed — 2026-06-20T00:00:00Z**
4+
> All priority recommendations (#1#5) plus the "worth adding" Create cost notice are **shipped**.
45
> - #1 — optional params (company, email, residential flag, pickup/packaging/label-stock,
56
> parcel dimensions) collapsed into an "Additional Fields" `collection` on Create + Get
67
> Rates. Create's flat panel drops from ~31 fields to ~14 + the collection.
78
> - #2 — Create `Service Type` defaults to `FEDEX_GROUND` (no more blank-but-required).
89
> - #3 — Phone Number description now states FedEx requires it.
10+
> - #4`${role} Country` is now an ISO 3166-1 dropdown (`COUNTRY_OPTIONS`, default US);
11+
> the value stays the two-letter code, so request bodies are unchanged.
12+
> - #5 — Label Stock Type now has a description; the dimensions all-or-nothing rule is
13+
> repeated on Length, Width, and Height (was only on Length).
14+
> - "Worth adding" (What NOT to touch #2) — Create now carries an inline `notice` that it
15+
> books a real shipment and bills the account (free in Sandbox, charged in Production).
916
>
1017
> Locked by characterization tests (`nodes/Fedex/resources/shipping/getRates.presend.test.mts`,
1118
> `create.presend.test.mts`) proving the emitted FedEx request bodies are byte-identical to
12-
> pre-refactor. `pnpm test` (16/16) / `build` / `lint` all green. Open: #4 (Country dropdown),
13-
> #5 (description normalization).
19+
> pre-refactor. `pnpm test` (16/16) / `build` / `lint` all green.
1420
1521
> Source: `/cognitive-load-conversion` skill, run 2026-06-19. Target = the n8n node
1622
> parameter panel as defined by the property builders in `nodes/Fedex/`. The property

nodes/Fedex/Fedex.node.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,27 @@ export class Fedex implements INodeType {
6969
],
7070
default: 'tracking',
7171
},
72+
// Credential-fit guidance. n8n type-filters the credential dropdown, so a Track
73+
// credential cannot attach to a Shipping operation (or vice versa) — the slot just
74+
// stays red with no explanation of why the credential "won't take". These per-resource
75+
// notices name the exact credential type each resource needs and point at the
76+
// separate-keys-per-project trap (ADR-0004), so the red indicator becomes self-explaining.
77+
{
78+
displayName:
79+
'This resource uses a <b>FedEx Track OAuth2 API</b> credential. If the credential field above is empty or shows a red mark, create or select one of that type — Track and Shipping require separate FedEx project keys, so a Shipping credential cannot be used here.',
80+
name: 'trackingCredentialNotice',
81+
type: 'notice',
82+
default: '',
83+
displayOptions: { show: { resource: ['tracking'] } },
84+
},
85+
{
86+
displayName:
87+
'These operations use a <b>FedEx Shipping OAuth2 API</b> credential. If the credential field above is empty or shows a red mark, create or select one of that type — Shipping and Track require separate FedEx project keys, so a Track credential cannot be used here.',
88+
name: 'shippingCredentialNotice',
89+
type: 'notice',
90+
default: '',
91+
displayOptions: { show: { resource: ['shipping'] } },
92+
},
7293
...trackingDescription,
7394
...shippingDescription,
7495
// Hidden auth discriminator the declarative routing engine reads to pick a credential.

nodes/Fedex/countries.ts

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import type { INodePropertyOptions } from 'n8n-workflow';
2+
3+
// ISO 3166-1 alpha-2 country codes, names from CLDR (en). The dropdown value is the same
4+
// two-letter code the free-text field previously accepted, so the emitted FedEx request body
5+
// is byte-identical — this is a presentational swap that removes ISO-code recall (audit #4,
6+
// docs/audits/cognitive-load-operation-ui-20260619-191805-1f35.md). Sorted by display name.
7+
export const COUNTRY_OPTIONS: INodePropertyOptions[] = [
8+
{ name: 'Afghanistan', value: 'AF' },
9+
{ name: 'Åland Islands', value: 'AX' },
10+
{ name: 'Albania', value: 'AL' },
11+
{ name: 'Algeria', value: 'DZ' },
12+
{ name: 'American Samoa', value: 'AS' },
13+
{ name: 'Andorra', value: 'AD' },
14+
{ name: 'Angola', value: 'AO' },
15+
{ name: 'Anguilla', value: 'AI' },
16+
{ name: 'Antarctica', value: 'AQ' },
17+
{ name: 'Antigua & Barbuda', value: 'AG' },
18+
{ name: 'Argentina', value: 'AR' },
19+
{ name: 'Armenia', value: 'AM' },
20+
{ name: 'Aruba', value: 'AW' },
21+
{ name: 'Australia', value: 'AU' },
22+
{ name: 'Austria', value: 'AT' },
23+
{ name: 'Azerbaijan', value: 'AZ' },
24+
{ name: 'Bahamas', value: 'BS' },
25+
{ name: 'Bahrain', value: 'BH' },
26+
{ name: 'Bangladesh', value: 'BD' },
27+
{ name: 'Barbados', value: 'BB' },
28+
{ name: 'Belarus', value: 'BY' },
29+
{ name: 'Belgium', value: 'BE' },
30+
{ name: 'Belize', value: 'BZ' },
31+
{ name: 'Benin', value: 'BJ' },
32+
{ name: 'Bermuda', value: 'BM' },
33+
{ name: 'Bhutan', value: 'BT' },
34+
{ name: 'Bolivia', value: 'BO' },
35+
{ name: 'Bosnia & Herzegovina', value: 'BA' },
36+
{ name: 'Botswana', value: 'BW' },
37+
{ name: 'Bouvet Island', value: 'BV' },
38+
{ name: 'Brazil', value: 'BR' },
39+
{ name: 'British Indian Ocean Territory', value: 'IO' },
40+
{ name: 'British Virgin Islands', value: 'VG' },
41+
{ name: 'Brunei', value: 'BN' },
42+
{ name: 'Bulgaria', value: 'BG' },
43+
{ name: 'Burkina Faso', value: 'BF' },
44+
{ name: 'Burundi', value: 'BI' },
45+
{ name: 'Cambodia', value: 'KH' },
46+
{ name: 'Cameroon', value: 'CM' },
47+
{ name: 'Canada', value: 'CA' },
48+
{ name: 'Cape Verde', value: 'CV' },
49+
{ name: 'Caribbean Netherlands', value: 'BQ' },
50+
{ name: 'Cayman Islands', value: 'KY' },
51+
{ name: 'Central African Republic', value: 'CF' },
52+
{ name: 'Chad', value: 'TD' },
53+
{ name: 'Chile', value: 'CL' },
54+
{ name: 'China', value: 'CN' },
55+
{ name: 'Christmas Island', value: 'CX' },
56+
{ name: 'Cocos (Keeling) Islands', value: 'CC' },
57+
{ name: 'Colombia', value: 'CO' },
58+
{ name: 'Comoros', value: 'KM' },
59+
{ name: 'Congo - Brazzaville', value: 'CG' },
60+
{ name: 'Congo - Kinshasa', value: 'CD' },
61+
{ name: 'Cook Islands', value: 'CK' },
62+
{ name: 'Costa Rica', value: 'CR' },
63+
{ name: 'Côte d’Ivoire', value: 'CI' },
64+
{ name: 'Croatia', value: 'HR' },
65+
{ name: 'Cuba', value: 'CU' },
66+
{ name: 'Curaçao', value: 'CW' },
67+
{ name: 'Cyprus', value: 'CY' },
68+
{ name: 'Czechia', value: 'CZ' },
69+
{ name: 'Denmark', value: 'DK' },
70+
{ name: 'Djibouti', value: 'DJ' },
71+
{ name: 'Dominica', value: 'DM' },
72+
{ name: 'Dominican Republic', value: 'DO' },
73+
{ name: 'Ecuador', value: 'EC' },
74+
{ name: 'Egypt', value: 'EG' },
75+
{ name: 'El Salvador', value: 'SV' },
76+
{ name: 'Equatorial Guinea', value: 'GQ' },
77+
{ name: 'Eritrea', value: 'ER' },
78+
{ name: 'Estonia', value: 'EE' },
79+
{ name: 'Eswatini', value: 'SZ' },
80+
{ name: 'Ethiopia', value: 'ET' },
81+
{ name: 'Falkland Islands', value: 'FK' },
82+
{ name: 'Faroe Islands', value: 'FO' },
83+
{ name: 'Fiji', value: 'FJ' },
84+
{ name: 'Finland', value: 'FI' },
85+
{ name: 'France', value: 'FR' },
86+
{ name: 'French Guiana', value: 'GF' },
87+
{ name: 'French Polynesia', value: 'PF' },
88+
{ name: 'French Southern Territories', value: 'TF' },
89+
{ name: 'Gabon', value: 'GA' },
90+
{ name: 'Gambia', value: 'GM' },
91+
{ name: 'Georgia', value: 'GE' },
92+
{ name: 'Germany', value: 'DE' },
93+
{ name: 'Ghana', value: 'GH' },
94+
{ name: 'Gibraltar', value: 'GI' },
95+
{ name: 'Greece', value: 'GR' },
96+
{ name: 'Greenland', value: 'GL' },
97+
{ name: 'Grenada', value: 'GD' },
98+
{ name: 'Guadeloupe', value: 'GP' },
99+
{ name: 'Guam', value: 'GU' },
100+
{ name: 'Guatemala', value: 'GT' },
101+
{ name: 'Guernsey', value: 'GG' },
102+
{ name: 'Guinea', value: 'GN' },
103+
{ name: 'Guinea-Bissau', value: 'GW' },
104+
{ name: 'Guyana', value: 'GY' },
105+
{ name: 'Haiti', value: 'HT' },
106+
{ name: 'Heard & McDonald Islands', value: 'HM' },
107+
{ name: 'Honduras', value: 'HN' },
108+
{ name: 'Hong Kong SAR China', value: 'HK' },
109+
{ name: 'Hungary', value: 'HU' },
110+
{ name: 'Iceland', value: 'IS' },
111+
{ name: 'India', value: 'IN' },
112+
{ name: 'Indonesia', value: 'ID' },
113+
{ name: 'Iran', value: 'IR' },
114+
{ name: 'Iraq', value: 'IQ' },
115+
{ name: 'Ireland', value: 'IE' },
116+
{ name: 'Isle of Man', value: 'IM' },
117+
{ name: 'Israel', value: 'IL' },
118+
{ name: 'Italy', value: 'IT' },
119+
{ name: 'Jamaica', value: 'JM' },
120+
{ name: 'Japan', value: 'JP' },
121+
{ name: 'Jersey', value: 'JE' },
122+
{ name: 'Jordan', value: 'JO' },
123+
{ name: 'Kazakhstan', value: 'KZ' },
124+
{ name: 'Kenya', value: 'KE' },
125+
{ name: 'Kiribati', value: 'KI' },
126+
{ name: 'Kuwait', value: 'KW' },
127+
{ name: 'Kyrgyzstan', value: 'KG' },
128+
{ name: 'Laos', value: 'LA' },
129+
{ name: 'Latvia', value: 'LV' },
130+
{ name: 'Lebanon', value: 'LB' },
131+
{ name: 'Lesotho', value: 'LS' },
132+
{ name: 'Liberia', value: 'LR' },
133+
{ name: 'Libya', value: 'LY' },
134+
{ name: 'Liechtenstein', value: 'LI' },
135+
{ name: 'Lithuania', value: 'LT' },
136+
{ name: 'Luxembourg', value: 'LU' },
137+
{ name: 'Macao SAR China', value: 'MO' },
138+
{ name: 'Madagascar', value: 'MG' },
139+
{ name: 'Malawi', value: 'MW' },
140+
{ name: 'Malaysia', value: 'MY' },
141+
{ name: 'Maldives', value: 'MV' },
142+
{ name: 'Mali', value: 'ML' },
143+
{ name: 'Malta', value: 'MT' },
144+
{ name: 'Marshall Islands', value: 'MH' },
145+
{ name: 'Martinique', value: 'MQ' },
146+
{ name: 'Mauritania', value: 'MR' },
147+
{ name: 'Mauritius', value: 'MU' },
148+
{ name: 'Mayotte', value: 'YT' },
149+
{ name: 'Mexico', value: 'MX' },
150+
{ name: 'Micronesia', value: 'FM' },
151+
{ name: 'Moldova', value: 'MD' },
152+
{ name: 'Monaco', value: 'MC' },
153+
{ name: 'Mongolia', value: 'MN' },
154+
{ name: 'Montenegro', value: 'ME' },
155+
{ name: 'Montserrat', value: 'MS' },
156+
{ name: 'Morocco', value: 'MA' },
157+
{ name: 'Mozambique', value: 'MZ' },
158+
{ name: 'Myanmar (Burma)', value: 'MM' },
159+
{ name: 'Namibia', value: 'NA' },
160+
{ name: 'Nauru', value: 'NR' },
161+
{ name: 'Nepal', value: 'NP' },
162+
{ name: 'Netherlands', value: 'NL' },
163+
{ name: 'New Caledonia', value: 'NC' },
164+
{ name: 'New Zealand', value: 'NZ' },
165+
{ name: 'Nicaragua', value: 'NI' },
166+
{ name: 'Niger', value: 'NE' },
167+
{ name: 'Nigeria', value: 'NG' },
168+
{ name: 'Niue', value: 'NU' },
169+
{ name: 'Norfolk Island', value: 'NF' },
170+
{ name: 'North Korea', value: 'KP' },
171+
{ name: 'North Macedonia', value: 'MK' },
172+
{ name: 'Northern Mariana Islands', value: 'MP' },
173+
{ name: 'Norway', value: 'NO' },
174+
{ name: 'Oman', value: 'OM' },
175+
{ name: 'Pakistan', value: 'PK' },
176+
{ name: 'Palau', value: 'PW' },
177+
{ name: 'Palestinian Territories', value: 'PS' },
178+
{ name: 'Panama', value: 'PA' },
179+
{ name: 'Papua New Guinea', value: 'PG' },
180+
{ name: 'Paraguay', value: 'PY' },
181+
{ name: 'Peru', value: 'PE' },
182+
{ name: 'Philippines', value: 'PH' },
183+
{ name: 'Pitcairn Islands', value: 'PN' },
184+
{ name: 'Poland', value: 'PL' },
185+
{ name: 'Portugal', value: 'PT' },
186+
{ name: 'Puerto Rico', value: 'PR' },
187+
{ name: 'Qatar', value: 'QA' },
188+
{ name: 'Réunion', value: 'RE' },
189+
{ name: 'Romania', value: 'RO' },
190+
{ name: 'Russia', value: 'RU' },
191+
{ name: 'Rwanda', value: 'RW' },
192+
{ name: 'Samoa', value: 'WS' },
193+
{ name: 'San Marino', value: 'SM' },
194+
{ name: 'São Tomé & Príncipe', value: 'ST' },
195+
{ name: 'Saudi Arabia', value: 'SA' },
196+
{ name: 'Senegal', value: 'SN' },
197+
{ name: 'Serbia', value: 'RS' },
198+
{ name: 'Seychelles', value: 'SC' },
199+
{ name: 'Sierra Leone', value: 'SL' },
200+
{ name: 'Singapore', value: 'SG' },
201+
{ name: 'Sint Maarten', value: 'SX' },
202+
{ name: 'Slovakia', value: 'SK' },
203+
{ name: 'Slovenia', value: 'SI' },
204+
{ name: 'Solomon Islands', value: 'SB' },
205+
{ name: 'Somalia', value: 'SO' },
206+
{ name: 'South Africa', value: 'ZA' },
207+
{ name: 'South Georgia & South Sandwich Islands', value: 'GS' },
208+
{ name: 'South Korea', value: 'KR' },
209+
{ name: 'South Sudan', value: 'SS' },
210+
{ name: 'Spain', value: 'ES' },
211+
{ name: 'Sri Lanka', value: 'LK' },
212+
{ name: 'St. Barthélemy', value: 'BL' },
213+
{ name: 'St. Helena', value: 'SH' },
214+
{ name: 'St. Kitts & Nevis', value: 'KN' },
215+
{ name: 'St. Lucia', value: 'LC' },
216+
{ name: 'St. Martin', value: 'MF' },
217+
{ name: 'St. Pierre & Miquelon', value: 'PM' },
218+
{ name: 'St. Vincent & Grenadines', value: 'VC' },
219+
{ name: 'Sudan', value: 'SD' },
220+
{ name: 'Suriname', value: 'SR' },
221+
{ name: 'Svalbard & Jan Mayen', value: 'SJ' },
222+
{ name: 'Sweden', value: 'SE' },
223+
{ name: 'Switzerland', value: 'CH' },
224+
{ name: 'Syria', value: 'SY' },
225+
{ name: 'Taiwan', value: 'TW' },
226+
{ name: 'Tajikistan', value: 'TJ' },
227+
{ name: 'Tanzania', value: 'TZ' },
228+
{ name: 'Thailand', value: 'TH' },
229+
{ name: 'Timor-Leste', value: 'TL' },
230+
{ name: 'Togo', value: 'TG' },
231+
{ name: 'Tokelau', value: 'TK' },
232+
{ name: 'Tonga', value: 'TO' },
233+
{ name: 'Trinidad & Tobago', value: 'TT' },
234+
{ name: 'Tunisia', value: 'TN' },
235+
{ name: 'Türkiye', value: 'TR' },
236+
{ name: 'Turkmenistan', value: 'TM' },
237+
{ name: 'Turks & Caicos Islands', value: 'TC' },
238+
{ name: 'Tuvalu', value: 'TV' },
239+
{ name: 'U.S. Outlying Islands', value: 'UM' },
240+
{ name: 'U.S. Virgin Islands', value: 'VI' },
241+
{ name: 'Uganda', value: 'UG' },
242+
{ name: 'Ukraine', value: 'UA' },
243+
{ name: 'United Arab Emirates', value: 'AE' },
244+
{ name: 'United Kingdom', value: 'GB' },
245+
{ name: 'United States', value: 'US' },
246+
{ name: 'Uruguay', value: 'UY' },
247+
{ name: 'Uzbekistan', value: 'UZ' },
248+
{ name: 'Vanuatu', value: 'VU' },
249+
{ name: 'Vatican City', value: 'VA' },
250+
{ name: 'Venezuela', value: 'VE' },
251+
{ name: 'Vietnam', value: 'VN' },
252+
{ name: 'Wallis & Futuna', value: 'WF' },
253+
{ name: 'Western Sahara', value: 'EH' },
254+
{ name: 'Yemen', value: 'YE' },
255+
{ name: 'Zambia', value: 'ZM' },
256+
{ name: 'Zimbabwe', value: 'ZW' },
257+
];

nodes/Fedex/fields.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
SERVICE_TYPE_OPTIONS,
77
WEIGHT_UNIT_OPTIONS,
88
} from './constants';
9+
import { COUNTRY_OPTIONS } from './countries';
910

1011
// Presentational INodeProperties builders (ADR-0003: the field surface stays declarative).
1112
// Roles namespace the shipper vs recipient parameters so both can live in one operation;
@@ -73,11 +74,12 @@ export function addressFields(role: Role, show: Show): INodeProperties[] {
7374
displayOptions: { show },
7475
},
7576
{
76-
displayName: `${cap(role)} Country Code`,
77+
displayName: `${cap(role)} Country`,
7778
name: `${role}CountryCode`,
78-
type: 'string',
79+
type: 'options',
80+
options: COUNTRY_OPTIONS,
7981
default: 'US',
80-
description: 'Two-letter ISO country code',
82+
description: 'Country of the address; the two-letter ISO code is what gets sent to FedEx',
8183
displayOptions: { show },
8284
},
8385
];
@@ -176,6 +178,8 @@ const dimensionEntries: INodeProperties[] = [
176178
type: 'number',
177179
default: 0,
178180
typeOptions: { minValue: 0 },
181+
description:
182+
'Dimensions are sent only when length, width, and height are all greater than zero',
179183
},
180184
{
181185
displayName: 'Length',
@@ -192,6 +196,8 @@ const dimensionEntries: INodeProperties[] = [
192196
type: 'number',
193197
default: 0,
194198
typeOptions: { minValue: 0 },
199+
description:
200+
'Dimensions are sent only when length, width, and height are all greater than zero',
195201
},
196202
];
197203

@@ -244,6 +250,7 @@ const labelStockEntry: INodeProperties = {
244250
// LABEL_STOCK_OPTIONS imported lazily below to keep the constant list co-located with Create.
245251
options: [],
246252
default: 'PAPER_4X6',
253+
description: 'Paper size or thermal stock the label prints on. Defaults to the standard 4x6.',
247254
};
248255

249256
/** Wrap a set of optional entries in the standard "Additional Fields" collection. */

nodes/Fedex/resources/shipping/create.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ import {
3131
const show = { resource: ['shipping'], operation: ['create'] };
3232

3333
export const createFields: INodeProperties[] = [
34+
// Honest friction (cognitive-load audit, "What NOT to touch" #2): Create buys a real
35+
// shipment and bills the configured account, and the node is usableAsTool, so an AI agent
36+
// can invoke it. Surface the cost up front rather than burying it in docs.
37+
{
38+
displayName:
39+
'Running this operation books a real FedEx shipment and bills the Shipping Account below. In Sandbox it is free; in Production it incurs charges.',
40+
name: 'createCostNotice',
41+
type: 'notice',
42+
default: '',
43+
displayOptions: { show },
44+
},
3445
accountNumberField(show),
3546
...addressFields('shipper', show),
3647
...contactFields('shipper', show),

0 commit comments

Comments
 (0)