Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions WORKLOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Worklog

## 2026-06-24

### Loc nx2 support (nx/blocks/loc, nx2-wired via importer pattern)
The Loc app runs on the nx2 layer (API/IMS/config/Lit, Spectrum 2 tokens, dark mode) but physically lives at `nx/blocks/loc` so the public path stays `/nx/blocks/loc`. The nx2 shell loads it via `NX_BLOCKS = new Set(['importer', 'loc'])` in `nx2/scripts/nx.js`, following the `importer` precedent.

- **Migration to nx2 layer.** Full app (dashboard, project wizard, connectors, DNT/regional-diff pipelines, 255 tests) moved onto the nx2 API/IMS/config/Lit stack.
- **Styling — pure nx2 tokens.** Rejected a `loc-tokens.css` nx compat shim; converted every Loc CSS file to Spectrum 2 tokens. `--spacing-*` → `--s2-spacing-*` (1:1); radius mapped by value; `--s2-font-size-600/800` (31/39px) → `--s2-heading-size-l/xl` (28/36px, accepted shrink — no 31/39px step); fuchsia/turquoise → nearest `--s2-pink-*`/`--s2-seafoam-*` (nx2 has neither).
- **Dark mode.** nx pinned `color-scheme: light`; nx2 is dark-aware, so hardcoded colors were tokenized. Structural neutrals → `--s2-*`/`light-dark()`; white-on-saturated foregrounds, translucent overlays, and box-shadows kept fixed (tokenizing flips them to near-black in dark). Loc inherits the scheme from the nx2 shell's own `color-scheme` handling; no Loc-specific override is needed.
- **Cross-tree imports (importer pattern).** Every `scripts/`, `utils/`, `public/` specifier (incl. `public/sl/components.js`) climbs one extra `../` + `nx2/` (e.g. `../../scripts/nx.js` → `../../../nx2/scripts/nx.js`); loc-internal imports and the `da-lit` bare specifier untouched. da.live serves both trees, so the relative climbs resolve at runtime.
- **Asset paths.** Loc's own assets (img incl. block-local `Smock_ChevronLeft`, `project/views/shared.*`, `setup/translate.json`, `connectors/google/translate.json`) use `new URL('…', import.meta.url).href` so they load from wherever loc physically lives; nx2 SDK refs (`${nxBase}/public/*`, `${nxBase}/styles/*`) stay pointed at `/nx2`. Removed now-unused `getConfig`/`nx` in `actions.js`, `options.js`, `utils/utils.js`. Expand chevrons switched `background-image` → `mask` + `currentcolor`; nav chevrons use `fill: currentcolor`.
- **Consumers/config.** `nx2/public/plugins/rollout/rollout.js` imports `/nx/blocks/loc/project/index.js`; dead `nx2/blocks/loc/*` entries dropped from `eslint.config.js`.

#### Backward compatibility under the legacy nx shell
The migration above broke Loc when loaded by the legacy `nx` shell (i.e. without the `nxver` switch that selects the nx2 shell).

1. **`nxBase` undefined → nx2 SDK assets 404.** Every Loc view module read `const { nxBase } = getConfig()` from `nx2/scripts/nx.js` at import time, but that config singleton is only populated by the nx2 shell's `setConfig`. Under the legacy shell `getConfig()` returns `{ error: 'Config not set, yet.' }`, so `nxBase` was `undefined` and all `${nxBase}/...` URLs (sl styles, `styles/buttons.js`) 404'd. Fix: added a shell-independent `export const nxBase` to `nx2/scripts/nx.js`, computed once from `import.meta.url` (same value `getConfig().nxBase` already returned; `setConfig`/`loadBlock` reuse the module const). All 13 Loc modules now `import { nxBase }` (aliased to `nx` where used) instead of destructuring from `getConfig()`, so the nx2 base resolves regardless of which shell loaded Loc. `getSvg` already takes explicit paths, so it was unaffected.

2. **IMS timeout → blank page (the real content blocker).** da.live `scripts.js` boots imslib via `initIms()` which imports `${getNx()}/utils/ims.js`. Without nxver `getNx()` is `…/nx`, so da.live uses **`nx/utils/ims.js`** (v1) while Loc's dashboard uses **`nx2/utils/ims.js`** — two separate module instances. The v1 instance boots imslib (sets `window.adobeIMS`); the nx2 instance then re-registers `window.adobeid` and its `onReady` never fires (imslib already initialized) → 5s reject → `dashboard.connectedCallback` → `ensureInitialized` throws → `_isLoading` stays true → blank. With nxver both sides resolve to the same `nx2/utils/ims.js` singleton, so it only broke on the legacy path. Fix in `nx2/utils/ims.js` `loadIms`: if imslib is already initialized (`window.adobeIMS?.initialized`) reuse the live session (`settleFromExisting`); if another loader already injected the imslib `<script>` but it isn't ready yet, poll on `initialized` (100ms, `IMS_TIMEOUT` cap) instead of registering our own `window.adobeid`. Gating on `initialized` (not the mere presence of `getAccessToken`) avoids reading a not-yet-validated token that would look anonymous. Single-instance (nxver) flow is unchanged.

3. **Missing `nx2/styles/buttons.css` → 404.** Loc loads `${nxBase}/styles/buttons.js` (→ `/nx2/styles/buttons.css`). Recreated it from the v1 `nx/styles/buttons.css` with nx2 tokens: `--body-font-family`→`--s2-font-family`, `--s2-radius-100/300`→`--s2-corner-radius-100/300`, bare `--s2-red`→`--s2-red-900`.

## 2026-06-23

### nx2/blocks/shared/dialog — configurable panel sizing (dialog-css-vars branch)
Expand Down
2 changes: 1 addition & 1 deletion nx/blocks/loc/connectors/glaas/api.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import makeBatches from '../../../../public/utils/batch.js';
import makeBatches from '../../../../../nx2/public/utils/batch.js';

export async function throttle(ms = 500) {
return new Promise((resolve) => {
Expand Down
2 changes: 1 addition & 1 deletion nx/blocks/loc/connectors/glaas/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Queue } from '../../../../public/utils/tree.js';
import { Queue } from '../../../../../nx2/public/utils/tree.js';
import {
checkSession, createTask, addAssets, updateStatus, getTask, downloadAsset,
prepareTargetPreview, getGlaasFilename,
Expand Down
10 changes: 5 additions & 5 deletions nx/blocks/loc/connectors/glaas/multimodalApi.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DA_ORIGIN } from '../../../../public/utils/constants.js';
import { Queue } from '../../../../public/utils/tree.js';
import { daFetch } from '../../../../utils/daFetch.js';
import { DA_ORIGIN } from '../../../../../nx2/public/utils/constants.js';
import { Queue } from '../../../../../nx2/public/utils/tree.js';
import { daFetch } from '../../../../../nx2/utils/api.js';
import {
buildGlaasCreateMetadata, getOpts, glaasSourcePreviewUrl, throttle,
} from './api.js';
Expand Down Expand Up @@ -650,7 +650,7 @@ async function fetchMultimodalImage({ imageIndex, imageUrl, logRequest }) {
logRequest?.('fetch-image', { imageIndex, contentDaLiveUrl: imageUrl, daSourceUrl: imageSourceUrl });
let imageResp;
try {
imageResp = await daFetch(imageSourceUrl);
imageResp = await daFetch({ url: imageSourceUrl });
} catch {
return { error: 'Error fetching content.da.live image.', step: `fetch-image-${imageIndex}` };
}
Expand Down Expand Up @@ -913,7 +913,7 @@ export async function postImageToDaMedia({
const body = new FormData();
body.append('data', data, mediaPath.split('/').pop());
try {
const resp = await daFetch(`${DA_ORIGIN}/media/${org}/${site}${mediaPath}`, { method: 'POST', body });
const resp = await daFetch({ url: `${DA_ORIGIN}/media/${org}/${site}${mediaPath}`, opts: { method: 'POST', body } });
if (!resp.ok) {
if (resp.status === 413) {
return skippedOversizedMediaUpload({ glaasName, sizeCheck });
Expand Down
13 changes: 4 additions & 9 deletions nx/blocks/loc/connectors/glaas/translationMetadata.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { DA_ORIGIN } from '../../../../public/utils/constants.js';
import { daFetch } from '../../../../utils/daFetch.js';
import { source } from '../../../../../nx2/utils/api.js';

const BLOCK_SCHEMA_PATH = '/.da/block-schema.json';
const SEO_GLOSSARY_PATH = '/.da/seo/glossary.json';
Expand Down Expand Up @@ -80,9 +79,8 @@ export function parseBlockSchema(schemaData) {
}

async function fetchJson(org, site, relativePath) {
const url = `${DA_ORIGIN}/source/${org}/${site}${relativePath}`;
try {
const resp = await daFetch(url);
const resp = await source.get({ org, site, path: relativePath });
if (!resp.ok) return null;
return resp.json();
} catch (error) {
Expand Down Expand Up @@ -111,18 +109,15 @@ export async function fetchKeywordsFile(org, site, pagePath) {
// Remove .html extension if present and add -keywords.json
const cleanPath = pagePath.replace(/\.html$/, '');
const keywordsPath = `${cleanPath}-keywords.json`;
// Try primary path
let url = `${DA_ORIGIN}/source/${org}/${site}${keywordsPath}`;
try {
const resp = await daFetch(url);
const resp = await source.get({ org, site, path: keywordsPath });
if (resp.ok) {
return resp.json();
}
// If 404 and path contains /langstore/, try fallback
if (resp.status === 404 && keywordsPath.includes('/langstore/')) {
const fallbackPath = keywordsPath.replace(/\/langstore\/[^/]+\//, '/');
url = `${DA_ORIGIN}/source/${org}/${site}${fallbackPath}`;
const fallbackResp = await daFetch(url);
const fallbackResp = await source.get({ org, site, path: fallbackPath });
if (fallbackResp.ok) {
return fallbackResp.json();
}
Expand Down
2 changes: 1 addition & 1 deletion nx/blocks/loc/connectors/google/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { addDnt, removeDnt } from '../../dnt/dnt.js';
import { Queue } from '../../../../public/utils/tree.js';
import { Queue } from '../../../../../nx2/public/utils/tree.js';
import { convertPath } from '../../utils/utils.js';

const MAX_LENGTH = 5000;
Expand Down
2 changes: 1 addition & 1 deletion nx/blocks/loc/connectors/smartling/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Queue } from '../../../../public/utils/tree.js';
import { Queue } from '../../../../../nx2/public/utils/tree.js';
import { addDnt, removeDnt } from '../../dnt/dnt.js';

export const dnt = { addDnt };
Expand Down
5 changes: 3 additions & 2 deletions nx/blocks/loc/connectors/trados/auth.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { daFetch } from '../../../../utils/daFetch.js';
import { daFetch } from '../../../../../nx2/utils/api.js';

const LOGIN_ORIGIN = 'https://da-etc.adobeaem.workers.dev';
const TOKEN_BUFFER = 300000; // 5 min buffer before expiry
Expand Down Expand Up @@ -32,7 +32,8 @@ export async function getAccessToken(service) {

const opts = { method: 'POST' };

const resp = await daFetch(`${LOGIN_ORIGIN}/${org}/sites/${site}/integrations/trados/login`, opts);
const url = `${LOGIN_ORIGIN}/${org}/sites/${site}/integrations/trados/login`;
const resp = await daFetch({ url, opts });
if (!resp.ok) return null;

const { access_token: accessToken, expires_in: expiresIn } = await resp.json();
Expand Down
2 changes: 1 addition & 1 deletion nx/blocks/loc/connectors/trados/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Queue } from '../../../../public/utils/tree.js';
import { Queue } from '../../../../../nx2/public/utils/tree.js';
import { addDnt, removeDnt } from '../../dnt/dnt.js';
import authReady, { getAccessToken } from './auth.js';
import { corsFetch } from './utils.js';
Expand Down
6 changes: 6 additions & 0 deletions nx/blocks/loc/img/Smock_ChevronLeft_18_N.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions nx/blocks/loc/loc.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
:host {
display: block;
color: var(--s2-gray-900);
font-size: var(--type-body-s-size);
font-size: var(--s2-body-size-s);
}

.nx-loc-step {
display: block;
max-width: var(--grid-container-width);
margin: 0 auto var(--spacing-500) auto;
max-width: var(--se-grid-container-width);
margin: 0 auto var(--s2-spacing-500) auto;
}
7 changes: 3 additions & 4 deletions nx/blocks/loc/loc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { LitElement, html, nothing } from 'da-lit';
import { getConfig } from '../../scripts/nexter.js';
import getStyle from '../../utils/styles.js';
import { nxBase as nx } from '../../../nx2/scripts/nx.js';
import getStyle from '../../../nx2/public/utils/styles.js';
import { getHashDetails, loadProject, updateProject } from './utils/utils.js';

import '../../public/sl/components.js';
import '../../../nx2/public/sl/components.js';

import './views/header/header.js';
import './views/steps/steps.js';
Expand All @@ -20,7 +20,6 @@ import './views/url-details/url-details.js';

const EL_NAME = 'nx-loc';

const { nxBase: nx } = getConfig();
const sl = await getStyle(`${nx}/public/sl/styles.css`);
const styles = await getStyle(import.meta.url);

Expand Down
60 changes: 21 additions & 39 deletions nx/blocks/loc/project/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import getElementMetadata from '../../../utils/getElementMetadata.js';
import getElementMetadata from '../../../../nx2/utils/getElementMetadata.js';
import { regionalDiff, removeLocTags } from '../regional-diff/regional-diff.js';
import { daFetch, saveToDa } from '../../../utils/daFetch.js';
import { DA_ORIGIN } from '../../../public/utils/constants.js';
import { Queue } from '../../../public/utils/tree.js';
import { source, versions } from '../../../../nx2/utils/api.js';
import { Queue } from '../../../../nx2/public/utils/tree.js';
import { saveToDa } from '../utils/save.js';

// Max concurrent /source/ reads from da-admin. Prevents flooding da-admin
// with OPTIONS+GET bursts during content scans (translate, rollout, validate).
Expand All @@ -27,12 +27,6 @@ const PARSER = new DOMParser();
let projPath;
let projJson;

async function fetchData(path) {
const resp = await daFetch(path);
if (!resp.ok) return null;
return resp.json();
}

export function formatDate(timestamp) {
const rawDate = timestamp ? new Date(timestamp) : new Date();
const date = rawDate.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' });
Expand Down Expand Up @@ -88,16 +82,17 @@ export async function detectService(config, env = 'stage') {

export async function getDetails() {
projPath = window.location.hash.replace('#', '');
const data = await fetchData(`${DA_ORIGIN}/source${projPath}.json`);
return data;
const resp = await source.get(`${projPath}.json`);
if (!resp.ok) return null;
return resp.json();
}

export function convertUrl({ path, srcLang, destLang }) {
const source = path.startsWith(srcLang) ? path : `${srcLang}${path}`;
const srcPath = path.startsWith(srcLang) ? path : `${srcLang}${path}`;
const destSlash = srcLang === '/' ? '/' : '';
const destination = path.startsWith(srcLang) ? path.replace(srcLang, `${destLang}${destSlash}`) : `${destLang}${path}`;

return { source, destination };
return { source: srcPath, destination };
}

export async function saveStatus(json) {
Expand All @@ -106,11 +101,8 @@ export async function saveStatus(json) {
projJson = copy;
const proj = JSON.parse(projJson);
proj.urls.forEach((url) => { delete url.content; });
const body = new FormData();
const file = new Blob([JSON.stringify(proj)], { type: 'application/json' });
body.append('data', file);
const opts = { body, method: 'POST' };
const resp = await daFetch(`${DA_ORIGIN}/source${projPath}.json`, opts);

const resp = await source.save(`${projPath}.json`, { body: JSON.stringify(proj) });
if (!resp.ok) return { error: 'Could not update project' };
return json;
}
Expand All @@ -123,9 +115,7 @@ async function saveVersion(path, label) {
if (versionSaving.has(path)) return;
versionSaving.add(path);
try {
const opts = { method: 'POST' };
if (label) opts.body = JSON.stringify({ label });
await daFetch(`${DA_ORIGIN}/versionsource${path}`, opts);
await versions.create(path, { comment: label });
} finally {
versionSaving.delete(path);
}
Expand All @@ -146,7 +136,7 @@ function collapseInnerTextSpaces(html) {

const getHtml = async (path, html) => {
const fetchHtml = async () => {
const res = await daFetch(`${DA_ORIGIN}/source${path}`);
const res = await source.get(path);
if (!res.ok) return null;
const str = await res.text();
return str;
Expand All @@ -166,14 +156,7 @@ const getDaUrl = (url) => {
export async function overwriteCopy(url, title) {
let resp;
if (url.sourceContent) {
const type = url.destination.includes('.json') ? 'application/json' : 'text/html';
const blob = new Blob([url.sourceContent], { type });
const opts = {
method: 'POST',
body: new FormData(),
};
opts.body.append('data', blob);
resp = await daFetch(`${DA_ORIGIN}/source${url.destination}`, opts);
resp = await source.save(url.destination, { body: url.sourceContent });
} else {
const srcHtml = await getHtml(url.source);
if (srcHtml) {
Expand All @@ -196,7 +179,7 @@ export async function overwriteCopy(url, title) {

url.status = 'success';
if (shouldSaveVersion(url)) {
saveVersion(url.destination, `${title} - Rolled Out`);
await saveVersion(url.destination, `${title} - Rolled Out`);
}
return resp;
}
Expand Down Expand Up @@ -332,14 +315,13 @@ export async function saveLangItems(sitePath, items, lang, removeDnt) {
const isJson = item.basePath.endsWith('.json');
const htmlToSave = await removeDnt(html, org, repo, { fileType: isJson ? 'json' : 'html' });

const blob = new Blob([htmlToSave], { type: isJson ? 'application/json' : 'text/html' });

const path = `${sitePath}${lang.location}${item.basePath}`;
const body = new FormData();
body.append('data', blob);
const opts = { body, method: 'POST' };
const resp = await daFetch(`${DA_ORIGIN}/source${path}`, opts);
results[idx] = { success: resp.status };
try {
const resp = await source.save(path, { body: htmlToSave });
results[idx] = { success: resp.status };
} catch {
results[idx] = { error: 'Could not save documents' };
}
}, MAX_CONCURRENT_WRITES);

await Promise.all(items.map((item, idx) => queue.push({ item, idx })));
Expand Down
14 changes: 7 additions & 7 deletions nx/blocks/loc/project/project.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
:host {
display: block;
max-width: var(--grid-container-width);
margin: var(--spacing-800) auto;
max-width: var(--se-grid-container-width);
margin: var(--s2-spacing-800) auto;
}

h1,
Expand All @@ -17,15 +17,15 @@ h6 {
nx-loc-sync,
nx-loc-translate,
nx-loc-rollout {
background: #f7f7f7;
background: var(--s2-gray-50);
display: block;
border-radius: 16px;
padding: var(--spacing-400);
margin-bottom: var(--spacing-400);
padding: var(--s2-spacing-400);
margin-bottom: var(--s2-spacing-400);
}

.da-loc-detail-org {
color: #676767;
color: var(--s2-gray-600);
text-transform: uppercase;
font-weight: 700;
margin: 0;
Expand All @@ -35,5 +35,5 @@ nx-loc-rollout {
.da-loc-detail-title {
line-height: 1;
font-size: 36px;
margin-bottom: var(--spacing-400);
margin-bottom: var(--s2-spacing-400);
}
4 changes: 2 additions & 2 deletions nx/blocks/loc/project/project.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LitElement, html, nothing } from '../../../deps/lit/dist/index.js';
import getStyle from '../../../utils/styles.js';
import { LitElement, html, nothing } from 'da-lit';
import getStyle from '../../../../nx2/public/utils/styles.js';
import { getDetails } from './index.js';

const style = await getStyle(import.meta.url);
Expand Down
17 changes: 8 additions & 9 deletions nx/blocks/loc/project/views/rollout.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import { LitElement, html, nothing } from '../../../../deps/lit/dist/index.js';
import { getConfig } from '../../../../scripts/nexter.js';
import { getSvg } from '../../../../utils/svg.js';
import getStyle from '../../../../utils/styles.js';
import { LitElement, html, nothing } from 'da-lit';
import { nxBase } from '../../../../../nx2/scripts/nx.js';
import { getSvg } from '../../../../../nx2/utils/svg.js';
import getStyle from '../../../../../nx2/public/utils/styles.js';
import {
calculateTime,
formatDate,
MAX_CONCURRENT_WRITES,
mergeCopy,
overwriteCopy,
saveStatus,
timeoutWrapper,
MAX_CONCURRENT_WRITES,
} from '../index.js';
import { Queue } from '../../../../public/utils/tree.js';
import { Queue } from '../../../../../nx2/public/utils/tree.js';

const { nxBase } = getConfig();
const style = await getStyle(import.meta.url);
const shared = await getStyle(`${nxBase}/blocks/loc/project/views/shared.js`);
const shared = await getStyle(new URL('./shared.js', import.meta.url).href);
const buttons = await getStyle(`${nxBase}/styles/buttons.js`);

const ICONS = [
`${nxBase}/blocks/loc/img/Smock_ChevronRight_18_N.svg`,
new URL('../../img/Smock_ChevronRight_18_N.svg', import.meta.url).href,
];

class NxLocRollout extends LitElement {
Expand Down
Loading