Skip to content

Commit 3ad9520

Browse files
dani-polaniclaude
andcommitted
fix: use pendingAlignmentColor in API so shared-token links get same color
Previously assignColorsInOrder gave each connection a sequential color, producing different colors when one word maps to multiple target words (e.g. "спать" → "to" + "sleep"). Now uses the same pendingAlignmentColor logic as the UI: if a token is already in a connected component, the new connection inherits that component's color. Also unifies the main-page footer with SiteFooter (adds API link, removes duplicate inline footer markup). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 989544b commit 3ad9520

3 files changed

Lines changed: 23 additions & 43 deletions

File tree

bitext/src/lib/api/align.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,22 @@ describe('buildAlignUrl', () => {
9898
expect(state.project.connections[0]!.lowerTokenId).toBe('l1-2'); // "monde"
9999
});
100100

101+
it('shared token gets the same color on both connections (many-to-one grouping)', () => {
102+
// "спать" → "to" and "спать" → "sleep": both connections must share a color
103+
const result = buildAlignUrl(ORIGIN, {
104+
lines: ['я хочу спать', 'I want to sleep'],
105+
alignments: [
106+
[0, 2, 1, 2],
107+
[0, 2, 1, 3]
108+
]
109+
});
110+
if (!('url' in result)) throw new Error('expected url');
111+
const state = decodeState(new URL(result.url).searchParams.get('data'));
112+
const conns = state.project.connections;
113+
expect(conns).toHaveLength(2);
114+
expect(conns[0]!.color).toBe(conns[1]!.color);
115+
});
116+
101117
it('handles 3 lines with alignments between each adjacent pair', () => {
102118
const result = buildAlignUrl(ORIGIN, {
103119
lines: ['A B', 'C D', 'E F'],

bitext/src/lib/api/align.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { encodeState } from '$lib/serialization/encode.js';
22
import { tokenize, tokenizeOptionsFromVisualSettings } from '$lib/domain/tokens.js';
3-
import { createConnectionId, type Connection } from '$lib/domain/alignment.js';
4-
import { assignColorsInOrder } from '$lib/domain/palettes.js';
3+
import { createConnectionId, pendingAlignmentColor, type Connection } from '$lib/domain/alignment.js';
54
import {
65
defaultVisualSettingsV2,
76
SCHEMA_VERSION,
@@ -67,7 +66,6 @@ export function buildAlignUrl(origin: string, req: AlignRequest): AlignResult {
6766
}));
6867

6968
const tokensByLine = lineObjects.map((line) => tokenize(line.rawText, line.id, tzOpts));
70-
const colors = assignColorsInOrder(settings.palette, Math.max(alignments.length, 1));
7169

7270
const connections: Connection[] = [];
7371
for (let idx = 0; idx < alignments.length; idx++) {
@@ -99,12 +97,10 @@ export function buildAlignUrl(origin: string, req: AlignRequest): AlignResult {
9997
err: `alignments[${idx}]: word ${lowerWordIdx} out of range for line ${lowerIdx} ("${lines[lowerIdx]}" has ${lowerTokens.length} word(s))`
10098
};
10199

102-
connections.push({
103-
id: createConnectionId(),
104-
upperTokenId: upperTokens[upperWordIdx]!.id,
105-
lowerTokenId: lowerTokens[lowerWordIdx]!.id,
106-
color: colors[idx % colors.length]
107-
});
100+
const upperTokenId = upperTokens[upperWordIdx]!.id;
101+
const lowerTokenId = lowerTokens[lowerWordIdx]!.id;
102+
const color = pendingAlignmentColor(connections, [upperTokenId], [lowerTokenId], settings.palette);
103+
connections.push({ id: createConnectionId(), upperTokenId, lowerTokenId, color });
108104
}
109105

110106
const state: AppStateV2 = {

bitext/src/routes/+page.svelte

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import { settingsStore } from '$lib/state/settings.svelte.js';
3535
import { editorExamples, type ExampleId } from '$lib/state/examples.js';
3636
import { TALLY_FORM_ID } from '$lib/brand.js';
37+
import SiteFooter from '$lib/components/layout/SiteFooter.svelte';
3738
import { DEFAULT_DESCRIPTION, DEFAULT_TITLE, SITE_NAME } from '$lib/seo/metadata.js';
3839
import type { PageProps } from './$types';
3940
import type { Component } from 'svelte';
@@ -125,7 +126,6 @@
125126
126127
const authorSite = 'https://danipolani.github.io/en/';
127128
const toolsPage = 'https://danipolani.github.io/en/blog/tools/';
128-
const year = new Date().getFullYear();
129129
130130
/** How much to trim from the top and bottom of each example image (CSS length, e.g. %, px). */
131131
const EXAMPLES_IMAGE_VERTICAL_CROP = '10%';
@@ -556,37 +556,5 @@
556556
<LineEditModal />
557557
<LineSettingsSheet />
558558

559-
<footer
560-
class="mt-12 border-t border-gray-200 pt-8 text-center text-base leading-relaxed text-gray-600 dark:border-gray-700 dark:text-gray-400"
561-
>
562-
<p>
563-
Created by
564-
<a
565-
href={authorSite}
566-
class="font-medium text-primary-700 underline decoration-primary-700/40 underline-offset-2 hover:text-primary-800 hover:decoration-primary-800 dark:text-primary-400 dark:decoration-primary-400/50 dark:hover:text-primary-300"
567-
target="_blank"
568-
rel="noopener noreferrer">Dani</a
569-
>. See other
570-
<a
571-
href={toolsPage}
572-
class="font-medium text-primary-700 underline decoration-primary-700/40 underline-offset-2 hover:text-primary-800 hover:decoration-primary-800 dark:text-primary-400 dark:decoration-primary-400/50 dark:hover:text-primary-300"
573-
target="_blank"
574-
rel="noopener noreferrer">tools</a
575-
> for linguistics and conlanging.
576-
</p>
577-
<p class="mt-2 text-gray-500 dark:text-gray-500">
578-
© {year} Dani Polani ·
579-
<a
580-
href={resolve('/about')}
581-
class="text-gray-600 underline decoration-gray-400/50 underline-offset-2 hover:text-gray-900 hover:decoration-gray-500/60 dark:text-gray-400 dark:decoration-gray-500/50 dark:hover:text-gray-200 dark:hover:decoration-gray-400/60"
582-
>About</a
583-
>
584-
·
585-
<a
586-
href={resolve('/privacy')}
587-
class="text-gray-600 underline decoration-gray-400/50 underline-offset-2 hover:text-gray-900 hover:decoration-gray-500/60 dark:text-gray-400 dark:decoration-gray-500/50 dark:hover:text-gray-200 dark:hover:decoration-gray-400/60"
588-
>Privacy policy</a
589-
>
590-
</p>
591-
</footer>
559+
<SiteFooter />
592560
</main>

0 commit comments

Comments
 (0)