Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5ee98c0
๐ŸŽจ Palette: ์–‘๋ฐฉํ–ฅ ํ…์ŠคํŠธ(BiDi) ์ง€์› ์ถ”๊ฐ€
seonghobae Aug 15, 2026
0795997
๐ŸŽจ Palette: ์–‘๋ฐฉํ–ฅ ํ…์ŠคํŠธ(BiDi) ์ง€์› ์ถ”๊ฐ€
seonghobae Aug 15, 2026
d257bc1
๐ŸŽจ Palette: ์–‘๋ฐฉํ–ฅ ํ…์ŠคํŠธ(BiDi) ์ง€์› ์ถ”๊ฐ€
seonghobae Aug 15, 2026
0a606d8
๐ŸŽจ Palette: ์–‘๋ฐฉํ–ฅ ํ…์ŠคํŠธ(BiDi) ์ง€์› ์ถ”๊ฐ€
seonghobae Aug 15, 2026
4c35f1a
test(security): preserve padded sensitive entry identity
seonghobae Aug 16, 2026
d405c3e
fix(security): retain exact padded entry names
seonghobae Aug 16, 2026
2def4a1
test(security): use JUnit 4 padded-name fixture
seonghobae Aug 16, 2026
5c5d99a
docs: make whitespace examples markdownlint-safe
seonghobae Aug 16, 2026
b5e84f4
๐ŸŽจ Palette: ์–‘๋ฐฉํ–ฅ ํ…์ŠคํŠธ(BiDi) ์ง€์› ๋ฐ ๋ณด์•ˆ/์ ‘๊ทผ์„ฑ ๊ฐœ์„  ์‚ฌํ•ญ ๋ณ‘ํ•ฉ
seonghobae Aug 18, 2026
075e8b2
test(a11y): require isolated bidi entry labels
seonghobae Aug 27, 2026
79f92bc
test(security): preserve padded sensitive identity
seonghobae Aug 27, 2026
4e29074
test(a11y): bind focus styling to visible entry text
seonghobae Aug 27, 2026
ada594b
fix(a11y): isolate bidi labels and exact exclusions
seonghobae Aug 27, 2026
f52e000
docs(a11y): trace bidi isolation and exact-name boundary
seonghobae Aug 27, 2026
17aa965
docs(changelog): record bidi and exact-name repairs
seonghobae Aug 27, 2026
3dc61ba
merge(master): record translatable-label reconciliation
seonghobae Aug 27, 2026
76a4dfb
๐ŸŽจ Palette: ์–‘๋ฐฉํ–ฅ ํ…์ŠคํŠธ(BiDi) ์ง€์› ๋ฐ ์ตœ์‹  ๊ฐœ์„  ์‚ฌํ•ญ ๋ณ‘ํ•ฉ
seonghobae Aug 27, 2026
5e71c87
๐ŸŽจ Palette: ์–‘๋ฐฉํ–ฅ ํ…์ŠคํŠธ(BiDi) ์ง€์› ๋ฐ ์ตœ์‹  ๊ฐœ์„  ์‚ฌํ•ญ ๋ณ‘ํ•ฉ
seonghobae Aug 27, 2026
defdbfd
๐ŸŽจ Palette: ์–‘๋ฐฉํ–ฅ ํ…์ŠคํŠธ(BiDi) ์ง€์› ๋ฐ ์ตœ์‹  ๊ฐœ์„  ์‚ฌํ•ญ ๋ณ‘ํ•ฉ
seonghobae Aug 27, 2026
73c74bb
๐ŸŽจ Palette: ์–‘๋ฐฉํ–ฅ ํ…์ŠคํŠธ(BiDi) ์ง€์› ๋ฐ ์ตœ์‹  ๊ฐœ์„  ์‚ฌํ•ญ ๋ณ‘ํ•ฉ
seonghobae Aug 27, 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
4 changes: 4 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@
## 2024-08-16 - ๋ช…ํ™•ํ•œ ํŽ˜์ด์ง€ ์ปจํ…์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๋ฌธ์„œ ์ œ๋ชฉ(Title) ๊ฐœ์„ 
**Learning:** ํŒŒ์ผ ์„œ๋ฒ„ ํƒ์ƒ‰ ์‹œ `<title>` ์š”์†Œ์— ๋‹จ์ˆœํžˆ ๋””๋ ‰ํ† ๋ฆฌ ์ด๋ฆ„๋งŒ ์ œ๊ณตํ•˜๋ฉด, ์‚ฌ์šฉ์ž๊ฐ€ ์—ฌ๋Ÿฌ ํƒญ์„ ์—ด์–ด๋‘์—ˆ๊ฑฐ๋‚˜ ํ™”๋ฉด ํŒ๋…๊ธฐ๋ฅผ ํ†ตํ•ด ์ฐฝ์„ ์ „ํ™˜ํ•  ๋•Œ ํ•ด๋‹น ํŽ˜์ด์ง€๊ฐ€ '๋””๋ ‰ํ† ๋ฆฌ ๋ชฉ๋ก'์ž„์„ ์ฆ‰์‹œ ์ธ์ง€ํ•˜๊ธฐ ์–ด๋ ค์›Œ ์ ‘๊ทผ์„ฑ ๋ฐ ์‚ฌ์šฉ์„ฑ์ด ์ €ํ•˜๋ฉ๋‹ˆ๋‹ค.
**Action:** ํ•ญ์ƒ `<title>` ์š”์†Œ์— ํŽ˜์ด์ง€์˜ ๋ชฉ์ ์„ ๋ช…ํ™•ํžˆ ์•Œ ์ˆ˜ ์žˆ๋Š” ์ ‘๋ฏธ์‚ฌ(์˜ˆ: " - ๋””๋ ‰ํ† ๋ฆฌ ๋ชฉ๋ก")๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ, ๋ธŒ๋ผ์šฐ์ € ํƒญ ์ด๋ฆ„๋งŒ์œผ๋กœ๋„ ์ปจํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์‹ญ์‹œ์˜ค.

## 2024-08-17 - ํ…์ŠคํŠธ ์ •๋ ฌ์„ ์œ„ํ•œ ์–‘๋ฐฉํ–ฅ ํ…์ŠคํŠธ(BiDi) ์ง€์› ์ถ”๊ฐ€
**Learning:** ํŒŒ์ผ ์„œ๋ฒ„ ํƒ์ƒ‰ ์‹œ ๋””๋ ‰ํ† ๋ฆฌ ๋˜๋Š” ํŒŒ์ผ ์ด๋ฆ„์ด ์šฐ์ธก์—์„œ ์ขŒ์ธก์œผ๋กœ(Right-To-Left) ์ฝํžˆ๋Š” ๋ฌธ์ž(์˜ˆ: ์•„๋ž์–ด, ํžˆ๋ธŒ๋ฆฌ์–ด ๋“ฑ)๋กœ ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜ ์„ž์—ฌ ์žˆ์„ ๋•Œ, ๊ธฐ๋ณธ ์ขŒ์ธก์—์„œ ์šฐ์ธก์œผ๋กœ(Left-To-Right) ํ๋ฆ„ ์•ˆ์—์„œ๋Š” ๋ ˆ์ด์•„์›ƒ๊ณผ ํ…์ŠคํŠธ๊ฐ€ ๊นจ์ ธ ๋ณด์ด๊ณ  ๊ฐ€๋…์„ฑ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
**Action:** ํ•ญ์ƒ ํŒŒ์ผ ์ด๋ฆ„, ๋””๋ ‰ํ† ๋ฆฌ ์ด๋ฆ„, ์„ค๋ช… ๋“ฑ ๋™์  ํ…์ŠคํŠธ๊ฐ€ ํฌํ•จ๋œ ์—˜๋ฆฌ๋จผํŠธ(`<h1>`, `<span>`)์— `dir="auto"` ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜์—ฌ, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ•ด๋‹น ์ฝ˜ํ…์ธ ์˜ ์–ธ์–ด ํŠน์„ฑ์— ๋งž์ถฐ ํ…์ŠคํŠธ ๋ฐฉํ–ฅ์„ ์ž๋™์œผ๋กœ ์ ์ ˆํ•˜๊ฒŒ ๋ Œ๋”๋งํ•˜๋„๋ก ๋ณด์žฅํ•˜์‹ญ์‹œ์˜ค.
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,7 @@
**Root cause:** The protected implementation added canonical names to the exclusion set but did not compare each observed directory entry through a locale-stable normalized key.
**Prevention:** Build one `Locale.ROOT` lowercase set from the canonical sensitive names, compare every observed name against it, and add the original spelling to the exclusion set so downstream exact membership remains correct.
**Evidence:** `testProcessIgnoreFileTreatsSensitiveNamesCaseInsensitively` failed on test-only commit `472b916cd40f70693c4e1eb48956042a25353feb` (CI run `31469596932`) and passed with the source fix at `bb113d858ccfc42ddaecf6729749b238e5ade2d0` (CI run `31469921661`).

## 2026-08-16 - ๊ณต๋ฐฑ ์šฐํšŒ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ํŒŒ์ผ ์ด๋ฆ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
**ํ•™์Šต:** ๋ณด์•ˆ ๋ชฉ์ ์œผ๋กœ ํŒŒ์ผ ์ด๋ฆ„์ด๋‚˜ ํ™•์žฅ์ž๋ฅผ ๋ฏผ๊ฐํ•œ ํŒŒ์ผ ๋ชฉ๋ก(์˜ˆ: `defaultSensitiveFiles`)๊ณผ ๋น„๊ตํ•˜์—ฌ ์ œ์™ธํ•  ๋•Œ, ํ™”์ดํŠธ์ŠคํŽ˜์ด์Šค(๊ณต๋ฐฑ ๋“ฑ)๊ฐ€ ํฌํ•จ๋œ ์ด๋ฆ„(์˜ˆ: ` .git` ๋˜๋Š” `config.json `)์€ ๋‹จ์ˆœ ๋ฌธ์ž์—ด ๋น„๊ต๋ฅผ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
Comment thread
seonghobae marked this conversation as resolved.
**์กฐ์น˜:** ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ๋‚˜ ํ•„ํ„ฐ๋ง ๊ทœ์น™๊ณผ ๋น„๊ตํ•˜๊ธฐ ์ „์— ํŒŒ์ผ ์ด๋ฆ„์— ๋Œ€ํ•ด `.trim()`์„ ํ˜ธ์ถœํ•˜์—ฌ, ์šฐ๋ฐœ์ ์ด๊ฑฐ๋‚˜ ์•…์˜์ ์ธ ํ™”์ดํŠธ์ŠคํŽ˜์ด์Šค ํŒจ๋”ฉ์œผ๋กœ ์ธํ•œ ์šฐํšŒ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ์ •ํ™•ํ•œ ๋ณด์•ˆ ์ •์ฑ…์„ ์ ์šฉํ•˜์‹ญ์‹œ์˜ค.
17 changes: 9 additions & 8 deletions src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array<String>? = null): S
// โšก Bolt Performance Optimization: ๋””๋ ‰ํ† ๋ฆฌ ๋ชฉ๋ก์„ Set์— ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ํ•„ํ„ฐ๋ง๋งŒ ํ•  ๋•Œ๋Š” ์ •๋ ฌ์ด ๋ถˆํ•„์š”ํ•˜๋ฏ€๋กœ .sorted()๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ O(N log N) ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
val list = dirFilesNames ?: curr_dir.list()
list?.forEach {
val current = it
val current = it.trim()
val pathCurrent = try {
java.nio.file.Paths.get(current)
} catch (_: java.nio.file.InvalidPathException) {
Expand All @@ -340,16 +340,17 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array<String>? = null): S

// ๋ณด์•ˆ ํ–ฅ์ƒ: dot-like prefixes and case variants of known sensitive names are excluded.
(dirFilesNames ?: curr_dir.list())?.forEach {
val normalizedName = it.toLowerCase(java.util.Locale.ROOT)
val fileName = it.trim()
val normalizedName = fileName.toLowerCase(java.util.Locale.ROOT)
if (
it.isHiddenFile() ||
fileName.isHiddenFile() ||
normalizedName in Constants.defaultSensitiveFileNamesLowercase ||
normalizedName.endsWith("~") ||
Constants.defaultSensitiveExtensions.any { extension ->
normalizedName.endsWith(extension)
}
) {
files_to_exclude.add(it)
files_to_exclude.add(fileName)
Comment thread
opencode-agent[bot] marked this conversation as resolved.
}
}

Expand Down Expand Up @@ -415,10 +416,10 @@ fun process_dir(curr_dir: File, excludeSet: Set<String>? = null, dirFiles: Array
</head>
<body>
<main>
<h1>${directoryName.escapeHtml()}</h1>
<h1 dir="auto">${directoryName.escapeHtml()}</h1>
<nav aria-label="๋””๋ ‰ํ† ๋ฆฌ ๋ชฉ๋ก">
<ul role="list">
<li><a class="dir-link" href="./.." aria-label="์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™" title="์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™"><span class="icon" aria-hidden="true">&#x21B0;</span> <span>..</span></a></li>
<li><a class="dir-link" href="./.." aria-label="์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™" title="์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™"><span class="icon" aria-hidden="true">&#x21B0;</span> <span dir="auto">..</span></a></li>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Master already moved the parent link to a .visually-hidden sibling and hid .. from assistive tech. Keep that pattern. dir="auto" on .. does nothing useful and fights the #459 translation contract.

"""

val index_middle = fun():String{
Expand Down Expand Up @@ -448,14 +449,14 @@ fun process_dir(curr_dir: File, excludeSet: Set<String>? = null, dirFiles: Array
val encodedHref = if (isLinkedDirectory) { "./${fileName.urlEncodePath()}/" } else { "./${fileName.urlEncodePath()}" }
val ariaLabel = "${fileName} ${if (isLinkedDirectory) { "๋””๋ ‰ํ† ๋ฆฌ" } else { "ํŒŒ์ผ" }}".escapeHtml()
val icon = if (isLinkedDirectory) { "&#128193;" } else { "&#128196;" }
l.append(""" <li><a class="dir-link" href="${encodedHref}" aria-label="${ariaLabel}" title="${ariaLabel}"><span class="icon" aria-hidden="true">${icon}</span> <span>${fileName.escapeHtml()}</span></a></li>""")
l.append(""" <li><a class="dir-link" href="${encodedHref}" aria-label="${ariaLabel}" title="${ariaLabel}"><span class="icon" aria-hidden="true">${icon}</span> <span dir="auto">${fileName.escapeHtml()}</span></a></li>""")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

dir="auto" isolates only this span. The concatenated aria-label / title (filename + ํŒŒ์ผ) can still be reordered by a leading RTL name, and this aria-label blocks browser translation (#459). Use class="entry-name" dir="auto", unicode-bidi: isolate, a sibling .visually-hidden type label, and FSI/PDI in title.

l.append('\n')
}
}
}

if(l.isEmpty()){
l.append(""" <li><div class="empty-dir" role="status"><span class="icon" aria-hidden="true">&#128194;</span> <span>์ด ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.</span></div></li>""")
l.append(""" <li><div class="empty-dir" role="status"><span class="icon" aria-hidden="true">&#128194;</span> <span dir="auto">์ด ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.</span></div></li>""")
l.append('\n')
}

Expand Down
4 changes: 2 additions & 2 deletions src/test/kotlin/html4tree/GeneratedIndexReadabilityTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class GeneratedIndexReadabilityTest {
)

val generatedHtml = generatedHtml()
val parentIndex = generatedHtml.indexOf("<span>..</span>")
val parentIndex = generatedHtml.indexOf("<span dir=\"auto\">..</span>")
val firstIndex = generatedHtml.indexOf("alpha.txt")
val middleIndex = generatedHtml.indexOf("middle.txt")
val lastIndex = generatedHtml.indexOf("zulu.txt")
Expand All @@ -62,7 +62,7 @@ class GeneratedIndexReadabilityTest {

val generatedHtml = generatedHtml()
val expectedEmptyRow =
"""<li><div class="empty-dir" role="status"><span class="icon" aria-hidden="true">&#128194;</span> <span>์ด ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.</span></div></li>"""
"""<li><div class="empty-dir" role="status"><span class="icon" aria-hidden="true">&#128194;</span> <span dir="auto">์ด ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.</span></div></li>"""

assertTrue(generatedHtml.contains(expectedEmptyRow))
assertTrue(generatedHtml.indexOf(expectedEmptyRow) == generatedHtml.lastIndexOf(expectedEmptyRow))
Expand Down
22 changes: 21 additions & 1 deletion src/test/kotlin/html4tree/MainTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,26 @@ class MainTest {
}
}

@Test
fun testProcessIgnoreFileExcludesSensitiveNamesWithWhitespace() {
val sensitiveNamesWithWhitespace = arrayOf(
" .git",
"config.json ",
"\tsecrets.yml",
".npmrc\r"
)
val safeNames = arrayOf(" public.txt ", "image.png")

val excluded = process_ignore_file(tempDir, sensitiveNamesWithWhitespace + safeNames)

sensitiveNamesWithWhitespace.forEach { name ->
assertTrue(excluded.contains(name.trim()), "'$name' must be excluded after trimming")
}
safeNames.forEach { name ->
assertFalse(excluded.contains(name.trim()), "'$name' must remain visible")
}
}

@Test
fun testGoIgnoresHiddenFilesAndDirectories() {
val hiddenFile = File(tempDir, ".hidden_file.txt")
Expand Down Expand Up @@ -943,7 +963,7 @@ class MainTest {
assertTrue(indexHtml.exists())
val content = indexHtml.readText()
assertTrue(content.contains("<title>Root - ๋””๋ ‰ํ† ๋ฆฌ ๋ชฉ๋ก</title>"))
assertTrue(content.contains("<h1>Root</h1>"))
assertTrue(content.contains("<h1 dir=\"auto\">Root</h1>"))
}

}
Loading