Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
144 commits
Select commit Hold shift + click to select a range
f4a0117
fix: added basicInfoFormValueSignal() as a tracked dependency so the …
ge94zec Apr 19, 2026
40d1edf
fix: run compliance analysis before translation for faster feedback
ge94zec Apr 20, 2026
edbea60
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec Apr 20, 2026
a7392d8
fix: double-trigger for translate -> race condition
ge94zec Apr 20, 2026
437788f
Merge remote-tracking branch 'origin/bugfix/2321-title-compliance-err…
ge94zec Apr 20, 2026
652bb5e
Added new categories in compliance sidebar
ge94zec Apr 24, 2026
22c7fe1
added new categories in sidebar
ge94zec Apr 26, 2026
a184f9d
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec Apr 27, 2026
343c8cc
fix highlights across reload
ge94zec Apr 28, 2026
7f5a227
Merge remote-tracking branch 'origin/bugfix/2321-title-compliance-err…
ge94zec Apr 28, 2026
b0840ab
prettier
ge94zec Apr 28, 2026
4a13ca2
esLint
ge94zec Apr 28, 2026
07b036c
Merge branch 'main' into feat/2345-add-new-compliance-categories-in-s…
ge94zec Apr 28, 2026
55ba4dd
Merge branch 'main' into feat/2345-add-new-compliance-categories-in-s…
ge94zec Apr 29, 2026
21e240e
esLint
ge94zec Apr 29, 2026
0c46d89
Merge remote-tracking branch 'origin/feat/2345-add-new-compliance-cat…
ge94zec Apr 29, 2026
e5d7de4
fix translations
ge94zec Apr 29, 2026
f1e62d4
prettier
ge94zec Apr 29, 2026
f241af0
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec Apr 29, 2026
1b847bd
change requests
ge94zec May 1, 2026
3c99c9f
Merge branch 'main' into feat/2345-add-new-compliance-categories-in-s…
ge94zec May 1, 2026
08537ab
Merge branch 'main' into feat/2345-add-new-compliance-categories-in-s…
az108 May 1, 2026
fba7bed
Merge branch 'main' into feat/2345-add-new-compliance-categories-in-s…
ge94zec May 1, 2026
ee05ead
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec May 1, 2026
5c6f1e2
Merge branch 'main' into feat/2345-add-new-compliance-categories-in-s…
ge94zec May 1, 2026
497111a
change requests
ge94zec May 1, 2026
53dd4e5
Merge remote-tracking branch 'origin/feat/2345-add-new-compliance-cat…
ge94zec May 1, 2026
dcf5420
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec May 1, 2026
08152f4
change requests
ge94zec May 1, 2026
ccdca07
eslint
ge94zec May 1, 2026
87ab90e
change requests
ge94zec May 1, 2026
dca0e2a
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec May 1, 2026
807e8d8
Merge branch 'main' into feat/2345-add-new-compliance-categories-in-s…
ge94zec May 1, 2026
9e23fd7
change requests
ge94zec May 1, 2026
1acbc29
Merge remote-tracking branch 'origin/bugfix/2321-title-compliance-err…
ge94zec May 1, 2026
7fe140f
Merge branch 'main' into feat/2345-add-new-compliance-categories-in-s…
ge94zec May 1, 2026
e08cc5b
Merge remote-tracking branch 'origin/main' into bugfix/2321-title-com…
ge94zec May 1, 2026
cd8b599
Merge branch 'feat/2345-add-new-compliance-categories-in-sidebar' int…
ge94zec May 2, 2026
8bb854e
feat: merge compliance add new categories
ge94zec May 2, 2026
0107acc
updated editor
ge94zec May 2, 2026
a8bf9bd
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec May 2, 2026
cc271f7
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec May 2, 2026
1f09ad4
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec May 2, 2026
2842223
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec May 2, 2026
6e1c011
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec May 3, 2026
f958bc2
Merge branch 'main' into bugfix/2321-title-compliance-error-not-shown
ge94zec May 3, 2026
5ac21ff
feat: move gender decoder analysis to server side
ge94zec May 3, 2026
c9f0d10
refactor: restructure server-side bias analysis and reduce excessive …
ge94zec May 3, 2026
203ca29
- processingOrder: genderDecoder -> original compliance and translate…
ge94zec May 3, 2026
8b22df9
Merge remote-tracking branch 'origin/main' into bugfix/2379-fix-exces…
ge94zec May 3, 2026
9a02e9b
updated openapi
ge94zec May 4, 2026
5eddb05
Merge remote-tracking branch 'origin/main' into bugfix/2379-fix-exces…
ge94zec May 4, 2026
158eec0
chore: update OpenAPI spec and generated client
github-actions[bot] May 4, 2026
8b7c4d4
fixed client and server tests
ge94zec May 4, 2026
7327454
Merge remote-tracking branch 'origin/bugfix/2379-fix-excessive-api-re…
ge94zec May 4, 2026
46b5c94
fixed client test
ge94zec May 4, 2026
04a26a6
fixed server test
ge94zec May 4, 2026
817ab36
Merge branch 'main' into bugfix/2379-fix-excessive-api-requests-trigg…
ge94zec May 8, 2026
343b52b
- prettier
ge94zec May 8, 2026
72c15c5
Merge remote-tracking branch 'origin/bugfix/2379-fix-excessive-api-re…
ge94zec May 8, 2026
ee58a1f
add doc
ge94zec May 8, 2026
c3fe888
refactor: remove GenderBiasAnalysisResource and GenderBiasAnalysisRe…
ge94zec May 9, 2026
7252ffa
Merge branch 'main' into bugfix/2379-fix-excessive-api-requests-trigg…
ge94zec May 9, 2026
9a2db45
Merge remote-tracking branch 'origin/main' into bugfix/2379-fix-exces…
ge94zec May 12, 2026
9f0aa5d
refactor: replace coding with enum GenderCategory
ge94zec May 12, 2026
0627177
\`Bugfix\`: Replace double Job load with a targeted biased-issues query
az108 May 12, 2026
c94c107
Merge branch 'main' into bugfix/2379-fix-excessive-api-requests-trigg…
az108 May 12, 2026
da449e8
fix editor
ge94zec May 13, 2026
a36dd36
Merge remote-tracking branch 'origin/main' into bugfix/2379-fix-exces…
ge94zec May 13, 2026
eb73e71
moved BiasWordList and GenderCategory
ge94zec May 13, 2026
7d9245b
prettier
ge94zec May 13, 2026
29c625b
Merge branch 'main' into bugfix/2379-fix-excessive-api-requests-trigg…
ge94zec May 14, 2026
cbe0b64
`Bugfix`: Store biased issues in a Set so Hibernate stops rewriting t…
az108 May 14, 2026
762be05
\`Bugfix\`: Carry the biased-issues Set type through the repo and DTO…
az108 May 14, 2026
be9f574
refactor complianceScore and tests
ge94zec May 14, 2026
e0f9c72
Merge remote-tracking branch 'origin/bugfix/2379-fix-excessive-api-re…
ge94zec May 14, 2026
2b77652
refactor complianceScore
ge94zec May 14, 2026
1e8ae67
prettier
ge94zec May 14, 2026
9c85baa
Merge branch 'main' into bugfix/2379-fix-excessive-api-requests-trigg…
ge94zec May 14, 2026
032eefe
Merge branch 'main' into bugfix/2379-fix-excessive-api-requests-trigg…
ge94zec May 15, 2026
e4cd714
Merge branch 'main' into bugfix/2379-fix-excessive-api-requests-trigg…
ge94zec May 17, 2026
e3634b0
- refactored namings and BiasedIssues to Set
ge94zec Jun 3, 2026
d5c024b
Merge remote-tracking branch 'origin/main' into bugfix/2379-fix-exces…
ge94zec Jun 4, 2026
3bfef56
chore: update OpenAPI spec and generated client
github-actions[bot] Jun 4, 2026
6ff3240
- fixed test names
ge94zec Jun 4, 2026
509a39a
Merge remote-tracking branch 'origin/bugfix/2379-fix-excessive-api-re…
ge94zec Jun 4, 2026
bdfb33a
- fixed AiResourceTest
ge94zec Jun 4, 2026
914b244
prettier
ge94zec Jun 4, 2026
f1ed30b
fix test
ge94zec Jun 4, 2026
8011d44
fix test
ge94zec Jun 9, 2026
775de1b
prettier
ge94zec Jun 9, 2026
71d4abb
Fix gender bias scoring persistence per job
ge94zec Jun 9, 2026
2174c4d
chore: update OpenAPI spec and generated client
github-actions[bot] Jun 9, 2026
880e9e7
Adjust tests and prettier
ge94zec Jun 9, 2026
536fbc2
Merge remote-tracking branch 'origin/bugfix/2379-fix-excessive-api-re…
ge94zec Jun 9, 2026
a3ecf73
lint
ge94zec Jun 9, 2026
0f65c7a
fix gender bias scoring, blank descriptions, and review feedback
ge94zec Aug 2, 2026
8b5da77
merge main
ge94zec Aug 3, 2026
8e0f3c6
fix(ai): persist and return multilingual job analysis consistently
ge94zec Aug 4, 2026
84eb1d8
chore: update OpenAPI spec and generated client
github-actions[bot] Aug 4, 2026
705e89e
fix server and client tests
ge94zec Aug 4, 2026
05e731b
Merge remote-tracking branch 'origin/bugfix/2379-fix-excessive-api-re…
ge94zec Aug 4, 2026
506a38b
feat: add genderDecoder UI in sidebar
ge94zec Aug 4, 2026
8e5d39e
Merge branch 'main' into bugfix/2379-fix-excessive-api-requests-trigg…
ge94zec Aug 10, 2026
b9fe961
fix: synchronize autosave, translation and compliance analysis
ge94zec Aug 10, 2026
aad62bf
chore: update OpenAPI spec and generated client
github-actions[bot] Aug 10, 2026
74ec58d
- fix server tests
ge94zec Aug 10, 2026
1cc7ad7
Merge remote-tracking branch 'origin/bugfix/2379-fix-excessive-api-re…
ge94zec Aug 10, 2026
a25e72d
Merge branch 'main' into bugfix/2379-fix-excessive-api-requests-trigg…
ge94zec Aug 13, 2026
811f5d0
fix: address review feedback for gender bias analysis
ge94zec Aug 13, 2026
c975f3b
prettier
ge94zec Aug 13, 2026
c1a3ef9
chore: update OpenAPI spec and generated client
github-actions[bot] Aug 13, 2026
581b926
test: drive editor tests through public inputs and template events
ge94zec Aug 13, 2026
73e5155
Merge remote-tracking branch 'origin/bugfix/2379-fix-excessive-api-re…
ge94zec Aug 13, 2026
dee54f3
chore: update OpenAPI spec and generated client
github-actions[bot] Aug 13, 2026
b2edf95
fix client test
ge94zec Aug 13, 2026
0c27f27
Merge remote-tracking branch 'origin/bugfix/2379-fix-excessive-api-re…
ge94zec Aug 13, 2026
7ea50d7
fix server test
ge94zec Aug 13, 2026
33b2fc2
optimized tests
ge94zec Aug 14, 2026
f84b5d0
fix: prevent AI compliance analysis without user consent
ge94zec Aug 15, 2026
232dbb9
fix client tests
ge94zec Aug 15, 2026
c52b9f8
prettier
ge94zec Aug 15, 2026
431156e
prettier
ge94zec Aug 17, 2026
1f1ac8e
test: compare parallel AI processing performance on test server
ge94zec Aug 17, 2026
ed0bcc8
Revert analysis and translation to sequential processing
ge94zec Aug 17, 2026
487bbcc
Merge branch 'main' into feat/2378-improve-genderdecoder-accuracy-usi…
ge94zec Aug 17, 2026
89f7d0e
feat: add gender decoder insights and filtering to AI sidebar
ge94zec Aug 17, 2026
46eb372
Merge remote-tracking branch 'origin/feat/2378-improve-genderdecoder-…
ge94zec Aug 17, 2026
3e7d3a0
`fix(ui): tighten gender bias filter typing and pill count coverage`
ge94zec Aug 17, 2026
8ebe8c4
Replace JobRepository.findByIdForAiUpdate with the inherited findById
ge94zec Aug 17, 2026
b55e5c0
fix(ui): correct gender bias highlights and loading feedback
ge94zec Aug 19, 2026
9031293
rename test
ge94zec Aug 19, 2026
23324c8
fix lighthouse scan
ge94zec Aug 19, 2026
bc60579
- add JavaDoc to GenderCategory
ge94zec Aug 21, 2026
f3e59e6
Merge branch 'main' into bugfix/2379-fix-excessive-api-requests-trigg…
ge94zec Aug 21, 2026
b085d5b
`General`: Move the rule based gender bias analysis out of the ai pac…
ge94zec Aug 21, 2026
a9bc453
Merge branch 'main' into feat/2378-improve-genderdecoder-accuracy-usi…
ge94zec Aug 22, 2026
7c06146
Merge remote-tracking branch 'origin/main' into bugfix/2379-fix-exces…
ge94zec Aug 22, 2026
09e2a5c
chore: update OpenAPI spec and generated client
github-actions[bot] Aug 22, 2026
90d3110
Merge branch 'feat/2378-improve-genderdecoder-accuracy-using-ai' into…
ge94zec Aug 22, 2026
bafc4e9
refactor: port gender decoder UI to biased issue contracts
ge94zec Aug 22, 2026
d34eb9a
prettier
ge94zec Aug 22, 2026
d6e384a
Merge remote-tracking branch 'origin/main' into feat/2637-port-new-ge…
ge94zec Aug 24, 2026
82d9587
fix: prevent partial-word gender highlights in the editor
ge94zec Aug 24, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

<changeSet id="053a_add_biased_issues_to_jobs" author="melissa">

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole file can go — it's a leftover from before 00000000000052_add_biased_issues_to_jobs.xml merged to main.

It is byte-identical to the merged 052, apart from the changeset id prefixes:

052a_add_biased_issues_to_jobs            ->  053a_add_biased_issues_to_jobs
052b_add_unique_constraint_biased_issues  ->  053b_add_unique_constraint_biased_issues
052c_rename_gender_bias_score_to_ai_score ->  053c_rename_gender_bias_score_to_ai_score

It is also not registered in master.xml — the diff doesn't touch that file at all — so it never runs.

Those two facts happen to cancel out into "harmless": as-is it is dead weight. But if someone later notices the missing include and "fixes" it, this would re-attempt createTable job_biased_issues and the gender_bias_score -> ai_score rename on a schema where 052 already did both. The preConditions onFail="MARK_RAN" guards would absorb it, so still no breakage — just a second changeset that does nothing. Cleanest is to delete the file rather than leave that trap.

job_biased_issues and ai_score are already on main via 052, so nothing here is needed.

<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="job_biased_issues"/>
</not>
</preConditions>
<createTable tableName="job_biased_issues">
<column name="job_id" type="CHAR(36)">
<constraints nullable="false" foreignKeyName="fk_biased_issues_job" references="jobs(job_id)" deleteCascade="true"/>
</column>
<column name="language" type="varchar(10)"/>
<column name="word" type="varchar(255)"/>
<column name="type" type="varchar(50)"/>
</createTable>
</changeSet>

<changeSet id="053b_add_unique_constraint_biased_issues" author="aniruddh">
<preConditions onFail="MARK_RAN">
<dbms type="mysql"/>
<not>
<uniqueConstraintExists tableName="job_biased_issues" constraintName="uk_biased_issues_job_lang_word_type"/>
</not>
</preConditions>
<addUniqueConstraint
tableName="job_biased_issues"
columnNames="job_id, language, word, type"
constraintName="uk_biased_issues_job_lang_word_type"/>
</changeSet>

<changeSet id="053c_rename_gender_bias_score_to_ai_score" author="melissa">
<preConditions onFail="MARK_RAN">
<columnExists tableName="jobs" columnName="gender_bias_score"/>
<not>
<columnExists tableName="jobs" columnName="ai_score"/>
</not>
</preConditions>
<renameColumn
tableName="jobs"
oldColumnName="gender_bias_score"
newColumnName="ai_score"
columnDataType="integer"/>
</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ <h1 jhiTranslate="{{ pageTitle() }}"></h1>
[shouldTranslate]="true"
[showGenderDecoderButton]="true"
[biasedAnalysis]="currentBiasedIssues()"
[showGenderBiasHighlights]="activeComplianceFilter() === undefined || activeComplianceFilter() === genderBiasFilter"
height="20rem"
(highlightHovered)="onHighlightHovered($event)"
/>
Expand Down Expand Up @@ -219,6 +220,8 @@ <h1 jhiTranslate="{{ pageTitle() }}"></h1>
[isRewriteMode]="rewriteButtonSignal()"
[currentLang]="currentDescriptionLanguage()"
[complianceIssues]="complianceIssues()"
[genderBiasAnalysis]="currentBiasedIssues()"
[isGenderAnalyzing]="isAnalyzing()"
(generate)="generateJobApplicationDraft()"
(filterComplianceCat)="onComplianceFilterChange($event)"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import {
import { CompliancePopoverComponent } from 'app/shared/components/molecules/ai-compliance-popover/ai-compliance-popover.component';
import { BiasedIssueDTO as BiasedIssue } from 'app/generated/model/biased-issue-dto';
import { AnalyzeJobDescriptionRequestDTO } from 'app/generated/model/analyze-job-description-request-dto';
import { FilterCategory, GENDER_BIAS_FILTER_CATEGORY } from 'app/shared/gender-bias-analysis/gender-bias-analysis.utils';

import { JobDetailComponent } from '../job-detail/job-detail.component';
import * as DropdownOptions from '.././dropdown-options';
Expand Down Expand Up @@ -342,7 +343,9 @@ export class JobCreationFormComponent {
readonly popoverY = signal<number>(0);

/** When set, only issues of this category are highlighted in the editor. (undefined = all categories shown) */
readonly activeComplianceFilter = signal<string | undefined>(undefined);
readonly activeComplianceFilter = signal<FilterCategory | undefined>(undefined);

protected readonly genderBiasFilter = GENDER_BIAS_FILTER_CATEGORY;

/** Returns the explanation of a compliance issue whose text appears in the job title, if any. */
readonly titleComplianceError = computed(() => {
Expand Down Expand Up @@ -976,7 +979,7 @@ export class JobCreationFormComponent {
* Handles category filter changes from the AI assistant sidebar.
* Updates filter signal to show only the selected category
*/
onComplianceFilterChange(category: string | undefined): void {
onComplianceFilterChange(category: FilterCategory | undefined): void {
this.activeComplianceFilter.set(category);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { ContentChange, QuillEditorComponent } from 'ngx-quill';
import { FormsModule } from '@angular/forms';
import { extractTextFromHtml } from 'app/shared/util/text.util';
import { BiasedIssueDTO as BiasedIssue, BiasedIssueDTOTypeEnum as BiasedIssueTypeEnum } from 'app/generated/model/biased-issue-dto';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { getUniqueNonInclusiveWords, isWordChar } from 'app/shared/gender-bias-analysis/gender-bias-analysis.utils';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs';
import Quill from 'quill';
import { GenderBiasAnalysisDialogComponent } from 'app/shared/gender-bias-analysis/gender-bias-analysis-dialog/gender-bias-analysis-dialog';
Expand Down Expand Up @@ -94,6 +95,39 @@ class HighlightBlot extends Inline {
// Register in Quill so the editor recognizes it
Quill.register(HighlightBlot);

/**
* Inline marker for wording flagged by the Gender Decoder. It is visually
* separate from compliance highlights so both can be rendered together.
*/
class GenderBiasHighlightBlot extends Inline {
static blotName = 'genderBiasHighlight';
static tagName = 'span';
static className = 'gender-bias-highlight';

static baseClasses = [
'[text-decoration-line:underline]',
'[text-decoration-style:wavy]',
'decoration-text-tertiary',
'[text-decoration-thickness:1.5px]',
'underline-offset-2',
'[box-decoration-break:clone]',
'[-webkit-box-decoration-break:clone]',
];

static create(): HTMLElement {
const node = super.create() as HTMLElement;
GenderBiasHighlightBlot.baseClasses.forEach((cls: string) => node.classList.add(cls));
node.dataset['genderBiasHighlight'] = 'non-inclusive';
return node;
}

static formats(node: HTMLElement): string | undefined {
return node.dataset['genderBiasHighlight'];
}
}

Quill.register(GenderBiasHighlightBlot);

const STANDARD_CHARACTER_LIMIT = 500;
const STANDARD_CHARACTER_BUFFER = 300;

Expand All @@ -119,20 +153,19 @@ export class EditorComponent extends BaseInputDirective<string> {
height = input<string>('12.5rem');
helperText = input<string | undefined>(undefined); // Optional helper text to display below the editor field
showGenderDecoderButton = input<boolean>(false);
showGenderBiasHighlights = input<boolean>(true);
// When true the editor is showing externally-streamed content (e.g. an AI
// translation); the empty/required error is suppressed so it does not flash
// while the first chunks arrive.
loading = input<boolean>(false);
genderDecoderClick = output<string>();
quillEditorComponent = viewChild(QuillEditorComponent);
highlightHovered = output<{ text: string; x: number; y: number } | undefined>();
pendingHighlights = signal<{ text: string; category: ComplianceIssueCategoryEnum }[]>([]);
biasedAnalysis = input<BiasedIssue[] | undefined>(undefined);
pendingComplianceHighlights = signal<{ text: string; category: ComplianceIssueCategoryEnum }[]>([]);

readonly cdRef = inject(ChangeDetectorRef);

readonly fieldIdChanges$ = toObservable(this.fieldId);

showAnalysisModal = signal(false);

displayHelperText = computed(() => {
Expand All @@ -145,6 +178,12 @@ export class EditorComponent extends BaseInputDirective<string> {
return this.showGenderDecoderButton() && this.displayResult() !== undefined;
});

readonly genderBiasHighlights = computed(() => {
if (!this.showGenderDecoderButton() || !this.showGenderBiasHighlights()) return [];

return getUniqueNonInclusiveWords(this.biasedAnalysis()).map(text => ({ text }));
});

// Check if error message should be displayed
isOverCharLimit = computed(() => {
const limit = this.characterLimit();
Expand Down Expand Up @@ -253,11 +292,13 @@ export class EditorComponent extends BaseInputDirective<string> {
* Re-runs highlight application whenever:
* - the QuillEditor view child becomes available
* - forceUpdate pushes new content (via editorReady)
* - new highlights are requested via highlightTexts()
* - new compliance highlights are requested via highlightTexts()
* - new Gender Decoder analysis results arrive
*/
private reapplyHighlightsEffect = effect(() => {
this.quillEditorComponent();
this.pendingHighlights();
this.pendingComplianceHighlights();
this.genderBiasHighlights();
requestAnimationFrame(() => this.applyPendingHighlights());
});

Expand Down Expand Up @@ -362,7 +403,7 @@ export class EditorComponent extends BaseInputDirective<string> {
* @param highlights Array of {text, category} to highlight
*/
public highlightTexts(highlights: { text: string; category: ComplianceIssueCategoryEnum }[]): void {
this.pendingHighlights.set(highlights);
this.pendingComplianceHighlights.set(highlights);
}

/**
Expand All @@ -372,20 +413,22 @@ export class EditorComponent extends BaseInputDirective<string> {
const editor = this.quillEditorComponent()?.quillEditor;
// Retry next frame if editor not ready and highlights pending
if (!editor) {
if (this.pendingHighlights().length > 0) {
if (this.pendingComplianceHighlights().length > 0 || this.genderBiasHighlights().length > 0) {
requestAnimationFrame(() => this.applyPendingHighlights());
}
return;
}
const highlights = this.pendingHighlights();
const complianceHighlights = this.pendingComplianceHighlights();
const genderBiasHighlights = this.genderBiasHighlights();

// Clear all existing highlights first
editor.formatText(0, editor.getLength(), 'background', false);
editor.formatText(0, editor.getLength(), 'customHighlight', false);
editor.formatText(0, editor.getLength(), 'genderBiasHighlight', false);

const fullText = editor.getText().toLowerCase();

for (const { text, category } of highlights) {
for (const { text, category } of complianceHighlights) {
const searchText = text.toLowerCase();
if (!searchText) continue;
let startIndex = 0;
Expand All @@ -398,6 +441,21 @@ export class EditorComponent extends BaseInputDirective<string> {
startIndex = index + text.length;
}
}

for (const { text } of genderBiasHighlights) {
const searchText = text.toLowerCase();
if (!searchText) continue;
let startIndex = 0;

while (startIndex < fullText.length) {
const index = fullText.indexOf(searchText, startIndex);
if (index === -1) break;
if (!isWordChar(fullText[index - 1]) && !isWordChar(fullText[index + searchText.length])) {
editor.formatText(index, searchText.length, 'genderBiasHighlight', true);
}
startIndex = index + searchText.length;
}
}
}

/**
Expand Down Expand Up @@ -433,18 +491,19 @@ export class EditorComponent extends BaseInputDirective<string> {
}

/**
* Removes compliance-highlight span wrappers from serialized editor HTML while
* Removes highlight span wrappers from serialized editor HTML while
* keeping their inner content. Highlights are a visual-only overlay, so their
* markup must never reach the form control or model value.
*
* @param html - The raw editor HTML, possibly containing highlight spans
* @returns The HTML with all compliance-highlight wrappers unwrapped
* @returns The HTML with all highlight wrappers unwrapped
*/
private stripHighlightMarkup(html: string): string {
if (!html.includes(HighlightBlot.className)) return html;
const highlightClasses = [HighlightBlot.className, GenderBiasHighlightBlot.className];
if (!highlightClasses.some(className => html.includes(className))) return html;
const container = document.createElement('div');
container.innerHTML = html;
container.querySelectorAll(`span.${HighlightBlot.className}`).forEach(span => {
container.querySelectorAll(highlightClasses.map(className => `span.${className}`).join(', ')).forEach(span => {
const parent = span.parentNode;
if (!parent) return;
// Unwrap the highlight span: move each child out in place, then drop the span.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,59 @@

<hr class="m-0 w-full border-0 border-t border-border-default opacity-70" />

<!-- Gender Decoder -->
<div class="flex items-center justify-center gap-2 lg:justify-start">
<h3
class="m-0 text-base font-semibold text-text-secondary text-center lg:text-left"
jhiTranslate="jobCreationForm.aiSidebar.genderDecoder.header"
></h3>
<jhi-info-icon tooltip="jobCreationForm.aiSidebar.genderDecoder.tooltipText" tooltipPosition="top" [shouldTranslate]="true" />
</div>

<div class="flex flex-col gap-3">
<p
class="m-0 text-sm leading-6 text-text-tertiary text-center lg:text-left"
jhiTranslate="jobCreationForm.aiSidebar.genderDecoder.relevanceText"
></p>

<jhi-status-pill
data-testid="gender-decoder-pill"
labelKey="jobCreationForm.aiSidebar.genderDecoder.pill.fix"
dotColor="bg-text-secondary"
[count]="genderDecoderReviewCount()"
[isActive]="activeFilter() === genderBiasFilter"
[loading]="isGenderAnalyzing()"
(selected)="selectCategoryFilter(genderBiasFilter)"
/>

<div class="flex w-full flex-col gap-1">
<p class="m-0 text-xs text-text-secondary" jhiTranslate="jobCreationForm.aiSidebar.genderDecoder.balanceLabel"></p>
<div>
<div class="relative h-4">
<div class="absolute left-0 right-0 top-1/2 flex -translate-y-1/2 items-center gap-px">
<span class="h-[2px] flex-1 rounded-full bg-border-default"></span>
<span class="h-[2px] flex-1 rounded-full bg-text-tertiary"></span>
<span class="h-[2px] flex-1 rounded-full bg-text-disabled"></span>
</div>
<span
data-testid="gender-decoder-pointer"
class="absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-background-default bg-text-secondary ring-1 ring-border-default"
aria-hidden="true"
[class]="genderDecoderPointerClass()"
></span>
</div>

<div class="flex items-center justify-between gap-2 text-xs leading-4 text-text-tertiary">
<span jhiTranslate="jobCreationForm.aiSidebar.genderDecoder.scale.exclusive"></span>
<span jhiTranslate="jobCreationForm.aiSidebar.genderDecoder.scale.neutral"></span>
<span jhiTranslate="jobCreationForm.aiSidebar.genderDecoder.scale.inclusive"></span>
</div>
</div>
</div>
</div>

<hr class="m-0 w-full border-0 border-t border-border-default opacity-70" />

<div class="flex items-start justify-center gap-2 lg:justify-start">
<h3
class="m-0 text-base font-semibold text-text-secondary text-center lg:text-left"
Expand Down
Loading
Loading