Compare commits

...

6 commits

Author SHA1 Message Date
Pascal Le Merrer
928e3afebd Track theme 2026-02-22 15:46:50 +01:00
Pascal Le Merrer
48932f2b80 Add articles to newsletter 12 2026-02-22 10:36:07 +01:00
Pascal Le Merrer
45bcde6d70 Add dependency to stork for search 2026-02-22 10:35:21 +01:00
Pascal Le Merrer
6ec0d96d43 Add newsletter #12 2026-02-18 14:14:44 +01:00
Pascal Le Merrer
b0fffe4f0a Add newsletter #11 2026-02-16 08:49:35 +01:00
Pascal Le Merrer
981140da28 Fix broken link 2026-02-09 19:00:29 +01:00
39 changed files with 1662 additions and 6 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

BIN
content/images/craftletter/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="29.989243mm"
height="29.989243mm"
viewBox="0 0 29.989243 29.989242"
version="1.1"
id="svg1"
sodipodi:docname="favicon.svg"
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showguides="true"
inkscape:zoom="2.0186728"
inkscape:cx="14.365874"
inkscape:cy="58.206559"
inkscape:window-width="2560"
inkscape:window-height="1351"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g19" />
<defs
id="defs1" />
<g
id="g19"
transform="translate(-6.494133,-6.033493)">
<circle
style="fill:#249bb3;fill-opacity:1;stroke-width:0.264583"
id="path22"
cx="21.488754"
cy="21.028114"
r="14.994621" />
<g
id="g18"
transform="matrix(1.0220008,0,0,1.0220008,4.9074989,-32.198722)"
style="fill:#ffffff">
<path
d="m 10.061962,40.811205 v 21.591448 h 13.81621 l -0.478,-3.432865 -9.06043,-0.03307 v -1.414384 h -1.91151 c -0.22141,0 -0.39946,-0.178053 -0.39946,-0.399459 0,-0.221405 0.17805,-0.399976 0.39946,-0.399976 h 1.91151 v -2.004012 h -1.91151 c -0.22141,0 -0.39946,-0.178569 -0.39946,-0.399975 0,-0.221405 0.17805,-0.399459 0.39946,-0.399459 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178054 -0.39946,-0.399459 0,-0.221406 0.17805,-0.399975 0.39946,-0.399975 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178054 -0.39946,-0.399459 0,-0.221406 0.17805,-0.399459 0.39946,-0.399459 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178053 -0.39946,-0.399458 0,-0.221406 0.17805,-0.399976 0.39946,-0.399976 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178054 -0.39946,-0.399459 0,-0.221406 0.17805,-0.399458 0.39946,-0.399458 h 1.91151 v -1.893426 z"
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;fill-opacity:1;stroke-width:2.53699"
id="path6" />
<path
d="m 14.339742,42.704631 h -1.91151 c -0.22141,0 -0.39946,0.178052 -0.39946,0.399458 0,0.221405 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:none;stroke-width:0.296737"
id="path8" />
<path
d="m 14.339742,45.508077 h -1.91151 c -0.22141,0 -0.39946,0.17857 -0.39946,0.399976 0,0.221405 0.17805,0.399458 0.39946,0.399458 h 1.91151 z"
style="fill:none;stroke-width:0.296737"
id="path10" />
<path
d="m 14.339742,48.31204 h -1.91151 c -0.22141,0 -0.39946,0.178053 -0.39946,0.399459 0,0.221405 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:none;stroke-width:0.296737"
id="path12" />
<path
d="m 14.339742,51.115487 h -1.91151 c -0.22141,0 -0.39946,0.178569 -0.39946,0.399975 0,0.221405 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:none;stroke-width:0.296737"
id="path14" />
<path
d="m 14.339742,53.91945 h -1.91151 c -0.22141,0 -0.39946,0.178054 -0.39946,0.399459 0,0.221406 0.17805,0.399975 0.39946,0.399975 h 1.91151 z"
style="fill:none;stroke-width:0.296737"
id="path16" />
<path
d="m 14.339742,56.722896 h -1.91151 c -0.22141,0 -0.39946,0.178571 -0.39946,0.399976 0,0.221406 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:none;stroke-width:0.296737"
id="path18" />
</g>
</g>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1" />
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="191.23006mm"
height="29.989243mm"
viewBox="0 0 191.23006 29.989242"
version="1.1"
id="svg1"
sodipodi:docname="horizontal.svg"
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showguides="true"
inkscape:zoom="2.0186728"
inkscape:cx="367.32054"
inkscape:cy="111.70706"
inkscape:window-width="2560"
inkscape:window-height="1351"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g18">
<sodipodi:guide
position="60.83391,3.6250775"
orientation="0,-1"
id="guide22"
inkscape:locked="false" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g19"
transform="translate(-6.494133,-6.033493)">
<rect
style="fill:#249bb3;fill-opacity:1;stroke-width:0.23553"
id="rect26-3-6"
width="95.615021"
height="29.989243"
x="6.494133"
y="6.033493"
ry="0.008925369" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 96.399823,31.800868 c -0.61408,0.40592 -0.66317,0.4146 -1.53746,0.6748 -0.87429,0.26021 -1.82143,0.39031 -2.84143,0.39031 -2.01919,0 -3.53878,-0.52041 -4.55878,-1.56122 -1.02,-1.05123 -1.54047,-2.44595 -1.53,-4.18409 l 0.0463,-7.17819 -0.0463,-7.71591 4.12163,0.0101 0.0463,4.80364 -5e-5,2.9021 -0.0463,7.16259 c -0.005,0.85345 0.20817,1.47796 0.62449,1.87347 0.41633,0.3955 1.08766,0.59327 2.01399,0.59327 0.59326,0 1.13449,-0.0677 1.62367,-0.20297 0.49959,-0.14571 0.95235,-0.32786 1.35827,-0.54643 z"
id="path12-9-4-7"
sodipodi:nodetypes="csscscccccccsssc" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 77.522843,8.8230973 c 1.10327,0 2.07643,0.0885 2.9195,0.26541 0.85347,0.17694 1.63408,0.41632 2.34183,0.71816 l -1.18653,2.7789807 c -0.55163,-0.22898 -1.12928,-0.39551 -1.73296,-0.49959 -0.60367,-0.10408 -1.20735,-0.15612 -1.81102,-0.15612 -1.03041,0 -1.785,0.19776 -2.26378,0.59326 -0.47877,0.38511 -0.71816,1.0044 -0.71816,1.85786 v 2.67561 l 19.601696,-0.0095 v 2.88231 l -19.601696,0.0096 v 12.45858 h -4.10603 v -12.45857 h -3.63765 v -2.88241 h 3.63765 v -2.76926 c 0,-1.59246 0.57766,-2.89868 1.73297,-3.918681 1.16571,-1.0304097 2.77377,-1.5456097 4.82418,-1.5456097 z"
id="path11-8-9-3"
sodipodi:nodetypes="ccccsscsccccccccccsccc" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 63.839893,28.041788 c 0,0.6349 0.0937,1.09807 0.28102,1.3895 0.18735,0.29143 0.48919,0.50479 0.90551,0.6401 l -0.0442,2.79812 c -1.56938,-0.001 -2.46937,-0.347 -3.09386,-0.69047 -0.62449,-0.35387 -1.10326,-0.87949 -1.43632,-1.57684 -0.6245,0.7598 -1.41552,1.32705 -2.37307,1.70174 -0.94714,0.37469 -1.94112,0.56204 -2.981938,0.56204 -1.686124,0 -3.023574,-0.47878 -4.01235,-1.43633 -0.988777,-0.95755 -1.483165,-2.20132 -1.483165,-3.73132 0,-1.75899 0.686939,-3.11205 2.060818,-4.05919 1.384287,-0.95755 3.341023,-1.43633 5.870205,-1.43633 h 2.31062 v -0.8899 c 0,-0.97837 -0.30704,-1.68612 -0.92113,-2.12327 -0.60367,-0.44755 -1.46755,-0.67132 -2.59163,-0.67132 -0.530817,0 -1.19694,0.0729 -1.998369,0.21857 -0.801429,0.13531 -1.618471,0.34867 -2.451124,0.6401 l -0.983572,-2.82583 c 1.051225,-0.3955 2.107655,-0.68693 3.169288,-0.87428 1.072041,-0.18734 2.045207,-0.28102 2.919487,-0.28102 2.32103,0 4.04358,0.49439 5.16766,1.48316 1.12408,0.97838 1.68612,2.36787 1.68612,4.16848 z"
id="path10-6-6-7"
sodipodi:nodetypes="sscccccssscscscsccccscss" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#249bb3;fill-opacity:1;stroke-width:2.53699"
d="m 56.439683,29.884038 c 0.62449,0 1.25419,-0.17173 1.88909,-0.5152 0.6349,-0.35388 1.13969,-0.85347 1.51439,-1.49878 v -3.21613 h -1.62368 c -1.53,0 -2.638471,0.24459 -3.32541,0.73378 -0.676531,0.48919 -1.014796,1.18133 -1.014796,2.07643 0,1.61327 0.85347,2.4199 2.560406,2.4199 z"
id="path9-5-3-2"
sodipodi:nodetypes="scccscss" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 32.598108,32.397608 v -2.85704 h 2.326226 v -10.83491 h -2.326226 v -2.84144 l 5.974025,0.0232 0.255265,3.77062 c 0.603674,-1.39469 1.36347,-2.45112 2.279389,-3.1693 0.915919,-0.71816 2.066022,-1.07724 3.450309,-1.07724 0.530817,0 0.999184,0.0416 1.405103,0.1249 0.405919,0.0833 0.801429,0.19255 1.186531,0.32785 l -0.432569,4.826 h -3.025794 l -0.02317,-1.60986 c -1.072041,0.0937 -1.998368,0.58286 -2.778981,1.46755 -0.780613,0.87429 -1.476612,2.07874 -1.924163,3.51507 l 0.08192,5.47759 h 3.294186 v 2.85704 z"
id="path8-0-7-6"
sodipodi:nodetypes="ccccccccssscccccccccc" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 21.842901,10.337487 c 1.498777,0 2.768574,0.197761 3.809391,0.593261 1.040817,0.38511 2.003572,0.94195 2.888267,1.67051 l -2.170104,2.57603 c -0.62449,-0.51 -1.301021,-0.9003 -2.029593,-1.17092 -0.728572,-0.28102 -1.509185,-0.42154 -2.341838,-0.42154 -1.009593,0 -1.93592,0.26541 -2.778982,0.79623 -0.832653,0.53082 -1.498776,1.38429 -1.99837,2.56041 -0.499592,1.16572 -0.749388,2.70613 -0.749388,4.62124 0,1.88388 0.239388,3.40868 0.718164,4.57439 0.489185,1.15531 1.150104,2.00357 1.982757,2.5448 0.843062,0.54122 1.79541,0.81183 2.857043,0.81183 1.124083,0 2.055614,-0.19255 2.794594,-0.57765 0.749389,-0.39551 1.415512,-0.83266 1.998369,-1.31143 l 2.013981,2.5448 c -0.759797,0.74939 -1.712144,1.38949 -2.857043,1.92031 -1.134491,0.53081 -2.513573,0.79622 -4.137248,0.79622 -1.883879,0 -3.564798,-0.42673 -5.04276,-1.2802 -1.47796,-0.86388 -2.638471,-2.13368 -3.481533,-3.80939 -0.843061,-1.68613 -1.264592,-3.75735 -1.264592,-6.21368 0,-2.4147 0.431939,-4.4547 1.295817,-6.12002 0.874286,-1.67571 2.050409,-2.94551 3.528369,-3.80939 1.48837,-0.86387 3.143269,-1.295811 4.964699,-1.295811 z"
id="text1-1-9-2-8-0"
sodipodi:nodetypes="sccccsccsccsccccsccsccs" />
</g>
<g
id="g18"
transform="translate(-6.4941406,-6.0334926)">
<rect
style="fill:#ffffff;stroke-width:0.23553"
id="rect26-3-6-5"
width="95.615021"
height="29.989243"
x="102.10917"
y="6.0334926"
ry="0.008925369"
inkscape:label="rect26-3-6-5" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#a02c2c;fill-opacity:1;stroke-width:2.53699"
d="m 167.25894,25.406429 c 0.1249,1.55082 0.58286,2.6697 1.37388,3.35664 0.79102,0.68693 1.75378,1.0304 2.88827,1.0304 0.79102,0 1.5352,-0.1249 2.23255,-0.37469 0.69735,-0.24979 1.38429,-0.59847 2.06082,-1.04602 l 1.22586,2.71789 c -0.81937,0.56339 -1.19983,0.80528 -2.27188,1.20079 -1.06163,0.39551 -2.23255,0.59326 -3.51276,0.59326 -1.79021,0 -3.29939,-0.36949 -4.52755,-1.10847 -1.21776,-0.73898 -2.13888,-1.76418 -2.76337,-3.07561 -0.62449,-1.31143 -0.93674,-2.82062 -0.93674,-4.52756 0,-1.64449 0.30184,-3.12765 0.90551,-4.44949 0.61409,-1.32184 1.49878,-2.36786 2.65409,-3.13807 1.16571,-0.78061 2.56561,-1.17092 4.19969,-1.17092 2.26899,0 4.0696,0.73898 5.40184,2.21695 1.33225,1.47796 1.99837,3.52316 1.99837,6.13561 0,0.60368 -0.026,1.10096 -0.0781,1.63929 z"
id="path19-4-4-6-5" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#a02c2c;fill-opacity:1;stroke-width:2.53699"
d="m 179.51963,32.453911 v -2.85704 h 2.32622 v -10.83491 h -2.32622 v -2.84144 l 5.97402,0.0232 0.25527,3.77062 c 0.60366,-1.39469 1.36347,-2.45112 2.27939,-3.1693 0.91591,-0.71816 2.06602,-1.07724 3.4503,-1.07724 0.53082,0 0.99919,0.0416 1.40511,0.1249 0.40592,0.0833 0.80143,0.19255 1.18653,0.32785 l -0.43257,4.826 h -3.02579 l -0.0232,-1.60986 c -1.07205,0.0937 -1.99837,0.58286 -2.77899,1.46755 -0.78061,0.87429 -1.47661,2.07874 -1.92416,3.51507 l 0.0819,5.47759 h 3.29418 v 2.85704 z"
id="path20-7-4-3" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#a02c2c;fill-opacity:1;stroke-width:2.53699"
d="m 124.31121,25.498691 c 0.1249,1.55082 0.58286,2.6697 1.37388,3.35664 0.79102,0.68693 1.75378,1.0304 2.88827,1.0304 0.79102,0 1.5352,-0.1249 2.23255,-0.37469 0.69735,-0.24979 1.38429,-0.59847 2.06082,-1.04602 l 1.22586,2.71789 c -0.81937,0.56339 -1.19983,0.80528 -2.27188,1.20079 -1.06163,0.39551 -2.23255,0.59326 -3.51276,0.59326 -1.79021,0 -3.29939,-0.36949 -4.52755,-1.10847 -1.21776,-0.73898 -2.13888,-1.76418 -2.76337,-3.07561 -0.62449,-1.31143 -0.93674,-2.82062 -0.93674,-4.52756 0,-1.64449 0.30184,-3.12765 0.90551,-4.44949 0.61409,-1.32184 1.49878,-2.36786 2.65409,-3.13807 1.16571,-0.78061 2.56561,-1.17092 4.19969,-1.17092 2.26899,0 4.0696,0.73898 5.40184,2.21695 1.33225,1.47796 1.99837,3.52316 1.99837,6.13561 0,0.60368 -0.026,1.10096 -0.0781,1.63929 z"
id="path19-4-4-6" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 127.85521,18.410731 c -0.99919,0 -1.82144,0.35908 -2.46675,1.07724 -0.6349,0.71817 -1.00959,1.84225 -1.12408,3.37225 h 6.96307 c -0.0208,-1.39469 -0.30702,-2.48235 -0.85867,-3.26296 -0.55163,-0.79102 -1.38949,-1.18653 -2.51357,-1.18653 z"
id="path18-2-6-1" />
<path
id="path17-7-0-5"
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#a02c2c;fill-opacity:1;stroke-width:2.53699"
d="m 140.17463,12.245649 v 4.827095 h -3.59048 v 2.887163 h 3.59048 v 7.179923 c 0,1.73817 0.51014,3.132494 1.53014,4.183724 1.02,1.04081 2.53971,1.561145 4.55889,1.561145 1.02001,0 1.96688,-0.129947 2.84117,-0.390157 0.87429,-0.2602 0.92381,-0.268974 1.53789,-0.674894 l -0.72605,-2.97863 c -0.40591,0.21857 -0.85847,0.401027 -1.35806,0.546737 -0.48919,0.13527 -1.03041,0.203088 -1.62367,0.203088 -0.92632,0 -1.59751,-0.197745 -2.01383,-0.593245 -0.41634,-0.39551 -0.62477,-1.020318 -0.62477,-1.873788 v -7.163903 h 7.73493 v 7.179923 c 0,1.73817 0.51014,3.132494 1.53014,4.183724 1.02,1.04081 2.5392,1.561145 4.55838,1.561145 1.02,0 1.96739,-0.129947 2.84168,-0.390157 0.87429,-0.2602 0.9233,-0.268974 1.53738,-0.674894 l -0.72554,-2.97863 c -0.40591,0.21857 -0.85846,0.401027 -1.35806,0.546737 -0.48919,0.13527 -1.0304,0.203088 -1.62367,0.203088 -0.92632,0 -1.59802,-0.197745 -2.01435,-0.593245 -0.41633,-0.39551 -0.62425,-1.020318 -0.62425,-1.873788 v -7.163903 h 5.04259 v -2.887163 h -5.04259 v -4.817277 l -4.12171,-0.0098 v 4.827095 h -7.73493 v -4.817277 z"
sodipodi:nodetypes="cccccscsscccsssccscsscccssscccccccccc" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 170.93889,18.844808 c -0.99919,0 -1.82143,0.35908 -2.46674,1.07724 -0.6349,0.71817 -1.00959,1.84225 -1.12408,3.37225 h 6.96306 c -0.0208,-1.39469 -0.30702,-2.48235 -0.85867,-3.26296 -0.55163,-0.79102 -1.38949,-1.18653 -2.51357,-1.18653 z"
id="path14-3-1-0" />
<path
d="M 105.67699,10.821962 V 32.41341 h 13.81621 l -0.478,-3.432865 -9.06043,-0.03307 v -1.414384 h -1.91151 c -0.22141,0 -0.39946,-0.178053 -0.39946,-0.399459 0,-0.221405 0.17805,-0.399976 0.39946,-0.399976 h 1.91151 v -2.004012 h -1.91151 c -0.22141,0 -0.39946,-0.178569 -0.39946,-0.399975 0,-0.221405 0.17805,-0.399459 0.39946,-0.399459 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178054 -0.39946,-0.399459 0,-0.221406 0.17805,-0.399975 0.39946,-0.399975 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178054 -0.39946,-0.399459 0,-0.221406 0.17805,-0.399459 0.39946,-0.399459 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178053 -0.39946,-0.399458 0,-0.221406 0.17805,-0.399976 0.39946,-0.399976 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178054 -0.39946,-0.399459 0,-0.221406 0.17805,-0.399458 0.39946,-0.399458 h 1.91151 v -1.893426 z"
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#a02c2c;fill-opacity:1;stroke-width:2.53699"
id="path6" />
<path
d="m 109.95477,12.715388 h -1.91151 c -0.22141,0 -0.39946,0.178052 -0.39946,0.399458 0,0.221405 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path8" />
<path
d="m 109.95477,15.518834 h -1.91151 c -0.22141,0 -0.39946,0.17857 -0.39946,0.399976 0,0.221405 0.17805,0.399458 0.39946,0.399458 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path10" />
<path
d="m 109.95477,18.322797 h -1.91151 c -0.22141,0 -0.39946,0.178053 -0.39946,0.399459 0,0.221405 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path12" />
<path
d="m 109.95477,21.126244 h -1.91151 c -0.22141,0 -0.39946,0.178569 -0.39946,0.399975 0,0.221405 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path14" />
<path
d="m 109.95477,23.930207 h -1.91151 c -0.22141,0 -0.39946,0.178054 -0.39946,0.399459 0,0.221406 0.17805,0.399975 0.39946,0.399975 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path16" />
<path
d="m 109.95477,26.733653 h -1.91151 c -0.22141,0 -0.39946,0.178571 -0.39946,0.399976 0,0.221406 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path18" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="95.615028mm"
height="59.978485mm"
viewBox="0 0 95.61503 59.978484"
version="1.1"
id="svg1"
sodipodi:docname="vertical.svg"
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
inkscape:export-filename="vertical.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showguides="true"
inkscape:zoom="1.0093364"
inkscape:cx="64.398747"
inkscape:cy="-93.625871"
inkscape:window-width="2560"
inkscape:window-height="1351"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g19">
<sodipodi:guide
position="60.83391,3.6250775"
orientation="0,-1"
id="guide22"
inkscape:locked="false" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g19"
transform="translate(-6.494133,-6.033493)">
<rect
style="fill:#249bb3;fill-opacity:1;stroke-width:0.23553"
id="rect26-3-6"
width="95.615021"
height="29.989243"
x="6.494133"
y="6.033493"
ry="0.008925369" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 96.399823,31.800868 c -0.61408,0.40592 -0.66317,0.4146 -1.53746,0.6748 -0.87429,0.26021 -1.82143,0.39031 -2.84143,0.39031 -2.01919,0 -3.53878,-0.52041 -4.55878,-1.56122 -1.02,-1.05123 -1.54047,-2.44595 -1.53,-4.18409 l 0.0463,-7.17819 -0.0463,-7.71591 4.12163,0.0101 0.0463,4.80364 -5e-5,2.9021 -0.0463,7.16259 c -0.005,0.85345 0.20817,1.47796 0.62449,1.87347 0.41633,0.3955 1.08766,0.59327 2.01399,0.59327 0.59326,0 1.13449,-0.0677 1.62367,-0.20297 0.49959,-0.14571 0.95235,-0.32786 1.35827,-0.54643 z"
id="path12-9-4-7"
sodipodi:nodetypes="csscscccccccsssc" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 77.522843,8.8230973 c 1.10327,0 2.07643,0.0885 2.9195,0.26541 0.85347,0.17694 1.63408,0.41632 2.34183,0.71816 l -1.18653,2.7789807 c -0.55163,-0.22898 -1.12928,-0.39551 -1.73296,-0.49959 -0.60367,-0.10408 -1.20735,-0.15612 -1.81102,-0.15612 -1.03041,0 -1.785,0.19776 -2.26378,0.59326 -0.47877,0.38511 -0.71816,1.0044 -0.71816,1.85786 v 2.67561 l 19.601696,-0.0095 v 2.88231 l -19.601696,0.0096 v 12.45858 h -4.10603 v -12.45857 h -3.63765 v -2.88241 h 3.63765 v -2.76926 c 0,-1.59246 0.57766,-2.89868 1.73297,-3.918681 1.16571,-1.0304097 2.77377,-1.5456097 4.82418,-1.5456097 z"
id="path11-8-9-3"
sodipodi:nodetypes="ccccsscsccccccccccsccc" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 63.839893,28.041788 c 0,0.6349 0.0937,1.09807 0.28102,1.3895 0.18735,0.29143 0.48919,0.50479 0.90551,0.6401 l -0.0442,2.79812 c -1.56938,-0.001 -2.46937,-0.347 -3.09386,-0.69047 -0.62449,-0.35387 -1.10326,-0.87949 -1.43632,-1.57684 -0.6245,0.7598 -1.41552,1.32705 -2.37307,1.70174 -0.94714,0.37469 -1.94112,0.56204 -2.981938,0.56204 -1.686124,0 -3.023574,-0.47878 -4.01235,-1.43633 -0.988777,-0.95755 -1.483165,-2.20132 -1.483165,-3.73132 0,-1.75899 0.686939,-3.11205 2.060818,-4.05919 1.384287,-0.95755 3.341023,-1.43633 5.870205,-1.43633 h 2.31062 v -0.8899 c 0,-0.97837 -0.30704,-1.68612 -0.92113,-2.12327 -0.60367,-0.44755 -1.46755,-0.67132 -2.59163,-0.67132 -0.530817,0 -1.19694,0.0729 -1.998369,0.21857 -0.801429,0.13531 -1.618471,0.34867 -2.451124,0.6401 l -0.983572,-2.82583 c 1.051225,-0.3955 2.107655,-0.68693 3.169288,-0.87428 1.072041,-0.18734 2.045207,-0.28102 2.919487,-0.28102 2.32103,0 4.04358,0.49439 5.16766,1.48316 1.12408,0.97838 1.68612,2.36787 1.68612,4.16848 z"
id="path10-6-6-7"
sodipodi:nodetypes="sscccccssscscscsccccscss" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#249bb3;fill-opacity:1;stroke-width:2.53699"
d="m 56.439683,29.884038 c 0.62449,0 1.25419,-0.17173 1.88909,-0.5152 0.6349,-0.35388 1.13969,-0.85347 1.51439,-1.49878 v -3.21613 h -1.62368 c -1.53,0 -2.638471,0.24459 -3.32541,0.73378 -0.676531,0.48919 -1.014796,1.18133 -1.014796,2.07643 0,1.61327 0.85347,2.4199 2.560406,2.4199 z"
id="path9-5-3-2"
sodipodi:nodetypes="scccscss" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 32.598108,32.397608 v -2.85704 h 2.326226 v -10.83491 h -2.326226 v -2.84144 l 5.974025,0.0232 0.255265,3.77062 c 0.603674,-1.39469 1.36347,-2.45112 2.279389,-3.1693 0.915919,-0.71816 2.066022,-1.07724 3.450309,-1.07724 0.530817,0 0.999184,0.0416 1.405103,0.1249 0.405919,0.0833 0.801429,0.19255 1.186531,0.32785 l -0.432569,4.826 h -3.025794 l -0.02317,-1.60986 c -1.072041,0.0937 -1.998368,0.58286 -2.778981,1.46755 -0.780613,0.87429 -1.476612,2.07874 -1.924163,3.51507 l 0.08192,5.47759 h 3.294186 v 2.85704 z"
id="path8-0-7-6"
sodipodi:nodetypes="ccccccccssscccccccccc" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 21.842901,10.337487 c 1.498777,0 2.768574,0.197761 3.809391,0.593261 1.040817,0.38511 2.003572,0.94195 2.888267,1.67051 l -2.170104,2.57603 c -0.62449,-0.51 -1.301021,-0.9003 -2.029593,-1.17092 -0.728572,-0.28102 -1.509185,-0.42154 -2.341838,-0.42154 -1.009593,0 -1.93592,0.26541 -2.778982,0.79623 -0.832653,0.53082 -1.498776,1.38429 -1.99837,2.56041 -0.499592,1.16572 -0.749388,2.70613 -0.749388,4.62124 0,1.88388 0.239388,3.40868 0.718164,4.57439 0.489185,1.15531 1.150104,2.00357 1.982757,2.5448 0.843062,0.54122 1.79541,0.81183 2.857043,0.81183 1.124083,0 2.055614,-0.19255 2.794594,-0.57765 0.749389,-0.39551 1.415512,-0.83266 1.998369,-1.31143 l 2.013981,2.5448 c -0.759797,0.74939 -1.712144,1.38949 -2.857043,1.92031 -1.134491,0.53081 -2.513573,0.79622 -4.137248,0.79622 -1.883879,0 -3.564798,-0.42673 -5.04276,-1.2802 -1.47796,-0.86388 -2.638471,-2.13368 -3.481533,-3.80939 -0.843061,-1.68613 -1.264592,-3.75735 -1.264592,-6.21368 0,-2.4147 0.431939,-4.4547 1.295817,-6.12002 0.874286,-1.67571 2.050409,-2.94551 3.528369,-3.80939 1.48837,-0.86387 3.143269,-1.295811 4.964699,-1.295811 z"
id="text1-1-9-2-8-0"
sodipodi:nodetypes="sccccsccsccsccccsccsccs" />
</g>
<g
id="g18"
transform="translate(-6.4941406,-6.0334926)">
<rect
style="fill:#ffffff;stroke-width:0.23553"
id="rect26-3-6-5"
width="95.615021"
height="29.989243"
x="6.4941444"
y="36.022736"
ry="0.008925369"
inkscape:label="rect26-3-6-5" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#a02c2c;fill-opacity:1;stroke-width:2.53699"
d="m 71.643915,55.395672 c 0.1249,1.55082 0.58286,2.6697 1.37388,3.35664 0.79102,0.68693 1.75378,1.0304 2.88827,1.0304 0.79102,0 1.5352,-0.1249 2.23255,-0.37469 0.69735,-0.24979 1.38429,-0.59847 2.06082,-1.04602 l 1.22586,2.71789 c -0.81937,0.56339 -1.19983,0.80528 -2.27188,1.20079 -1.06163,0.39551 -2.23255,0.59326 -3.51276,0.59326 -1.79021,0 -3.29939,-0.36949 -4.52755,-1.10847 -1.21776,-0.73898 -2.13888,-1.76418 -2.76337,-3.07561 -0.62449,-1.31143 -0.93674,-2.82062 -0.93674,-4.52756 0,-1.64449 0.30184,-3.12765 0.90551,-4.44949 0.61409,-1.32184 1.49878,-2.36786 2.65409,-3.13807 1.16571,-0.78061 2.56561,-1.17092 4.19969,-1.17092 2.26899,0 4.0696,0.73898 5.40184,2.21695 1.33225,1.47796 1.99837,3.52316 1.99837,6.13561 0,0.60368 -0.026,1.10096 -0.0781,1.63929 z"
id="path19-4-4-6-5" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#a02c2c;fill-opacity:1;stroke-width:2.53699"
d="m 83.904605,62.443154 v -2.85704 h 2.32622 v -10.83491 h -2.32622 v -2.84144 l 5.97402,0.0232 0.25527,3.77062 c 0.60366,-1.39469 1.36347,-2.45112 2.27939,-3.1693 0.91591,-0.71816 2.06602,-1.07724 3.4503,-1.07724 0.53082,0 0.99919,0.0416 1.40511,0.1249 0.40592,0.0833 0.80143,0.19255 1.18653,0.32785 l -0.43257,4.826 h -3.02579 l -0.0232,-1.60986 c -1.07205,0.0937 -1.99837,0.58286 -2.77899,1.46755 -0.78061,0.87429 -1.47661,2.07874 -1.92416,3.51507 l 0.0819,5.47759 h 3.29418 v 2.85704 z"
id="path20-7-4-3" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#a02c2c;fill-opacity:1;stroke-width:2.53699"
d="m 28.696185,55.487934 c 0.1249,1.55082 0.58286,2.6697 1.37388,3.35664 0.79102,0.68693 1.75378,1.0304 2.88827,1.0304 0.79102,0 1.5352,-0.1249 2.23255,-0.37469 0.69735,-0.24979 1.38429,-0.59847 2.06082,-1.04602 l 1.22586,2.71789 c -0.81937,0.56339 -1.19983,0.80528 -2.27188,1.20079 -1.06163,0.39551 -2.23255,0.59326 -3.51276,0.59326 -1.79021,0 -3.29939,-0.36949 -4.52755,-1.10847 -1.21776,-0.73898 -2.13888,-1.76418 -2.76337,-3.07561 -0.62449,-1.31143 -0.93674,-2.82062 -0.93674,-4.52756 0,-1.64449 0.30184,-3.12765 0.90551,-4.44949 0.61409,-1.32184 1.49878,-2.36786 2.65409,-3.13807 1.16571,-0.78061 2.56561,-1.17092 4.19969,-1.17092 2.26899,0 4.0696,0.73898 5.40184,2.21695 1.33225,1.47796 1.99837,3.52316 1.99837,6.13561 0,0.60368 -0.026,1.10096 -0.0781,1.63929 z"
id="path19-4-4-6" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 32.240185,48.399974 c -0.99919,0 -1.82144,0.35908 -2.46675,1.07724 -0.6349,0.71817 -1.00959,1.84225 -1.12408,3.37225 h 6.96307 c -0.0208,-1.39469 -0.30702,-2.48235 -0.85867,-3.26296 -0.55163,-0.79102 -1.38949,-1.18653 -2.51357,-1.18653 z"
id="path18-2-6-1" />
<path
id="path17-7-0-5"
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#a02c2c;fill-opacity:1;stroke-width:2.53699"
d="m 44.559605,42.234892 v 4.827095 h -3.59048 v 2.887163 h 3.59048 v 7.179923 c 0,1.73817 0.51014,3.132494 1.53014,4.183724 1.02,1.04081 2.53971,1.561145 4.55889,1.561145 1.02001,0 1.96688,-0.129947 2.84117,-0.390157 0.87429,-0.2602 0.92381,-0.268974 1.53789,-0.674894 l -0.72605,-2.97863 c -0.40591,0.21857 -0.85847,0.401027 -1.35806,0.546737 -0.48919,0.13527 -1.03041,0.203088 -1.62367,0.203088 -0.92632,0 -1.59751,-0.197745 -2.01383,-0.593245 -0.41634,-0.39551 -0.62477,-1.020318 -0.62477,-1.873788 V 49.94915 h 7.73493 v 7.179923 c 0,1.73817 0.51014,3.132494 1.53014,4.183724 1.02,1.04081 2.5392,1.561145 4.55838,1.561145 1.02,0 1.96739,-0.129947 2.84168,-0.390157 0.87429,-0.2602 0.9233,-0.268974 1.53738,-0.674894 l -0.72554,-2.97863 c -0.40591,0.21857 -0.85846,0.401027 -1.35806,0.546737 -0.48919,0.13527 -1.0304,0.203088 -1.62367,0.203088 -0.92632,0 -1.59802,-0.197745 -2.01435,-0.593245 -0.41633,-0.39551 -0.62425,-1.020318 -0.62425,-1.873788 V 49.94915 h 5.04259 v -2.887163 h -5.04259 V 42.24471 l -4.12171,-0.0098 v 4.827095 h -7.73493 v -4.817277 z"
sodipodi:nodetypes="cccccscsscccsssccscsscccssscccccccccc" />
<path
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#ffffff;stroke-width:2.53699"
d="m 75.323865,48.834051 c -0.99919,0 -1.82143,0.35908 -2.46674,1.07724 -0.6349,0.71817 -1.00959,1.84225 -1.12408,3.37225 h 6.96306 c -0.0208,-1.39469 -0.30702,-2.48235 -0.85867,-3.26296 -0.55163,-0.79102 -1.38949,-1.18653 -2.51357,-1.18653 z"
id="path14-3-1-0" />
<path
d="m 10.061965,40.811205 v 21.591448 h 13.81621 l -0.478,-3.432865 -9.06043,-0.03307 v -1.414384 h -1.91151 c -0.22141,0 -0.39946,-0.178053 -0.39946,-0.399459 0,-0.221405 0.17805,-0.399976 0.39946,-0.399976 h 1.91151 v -2.004012 h -1.91151 c -0.22141,0 -0.39946,-0.178569 -0.39946,-0.399975 0,-0.221405 0.17805,-0.399459 0.39946,-0.399459 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178054 -0.39946,-0.399459 0,-0.221406 0.17805,-0.399975 0.39946,-0.399975 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178054 -0.39946,-0.399459 0,-0.221406 0.17805,-0.399459 0.39946,-0.399459 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178053 -0.39946,-0.399458 0,-0.221406 0.17805,-0.399976 0.39946,-0.399976 h 1.91151 v -2.004529 h -1.91151 c -0.22141,0 -0.39946,-0.178054 -0.39946,-0.399459 0,-0.221406 0.17805,-0.399458 0.39946,-0.399458 h 1.91151 v -1.893426 z"
style="font-weight:600;font-size:30.4439px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Semi-Bold';fill:#a02c2c;fill-opacity:1;stroke-width:2.53699"
id="path6" />
<path
d="m 14.339745,42.704631 h -1.91151 c -0.22141,0 -0.39946,0.178052 -0.39946,0.399458 0,0.221405 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path8" />
<path
d="m 14.339745,45.508077 h -1.91151 c -0.22141,0 -0.39946,0.17857 -0.39946,0.399976 0,0.221405 0.17805,0.399458 0.39946,0.399458 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path10" />
<path
d="m 14.339745,48.31204 h -1.91151 c -0.22141,0 -0.39946,0.178053 -0.39946,0.399459 0,0.221405 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path12" />
<path
d="m 14.339745,51.115487 h -1.91151 c -0.22141,0 -0.39946,0.178569 -0.39946,0.399975 0,0.221405 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path14" />
<path
d="m 14.339745,53.91945 h -1.91151 c -0.22141,0 -0.39946,0.178054 -0.39946,0.399459 0,0.221406 0.17805,0.399975 0.39946,0.399975 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path16" />
<path
d="m 14.339745,56.722896 h -1.91151 c -0.22141,0 -0.39946,0.178571 -0.39946,0.399976 0,0.221406 0.17805,0.399459 0.39946,0.399459 h 1.91151 z"
style="fill:#ffffff;stroke-width:0.296737"
id="path18" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -27,7 +27,7 @@ vous pouvez consulter les résultats du sondage [State of JS 2025](https://2025.
Quelques points qui mont interpelés :
* Angular est toujours aussi peu apprécié : plus dune personne sur deux qui la essayé na pas aimé lexpérience.
* Le taux de satisfaction des utilisateurs de HTMX est en baisse (de 84% en 2016 à 69% en 2025) ; je soupçonne que cela correspond à la découverte des limitations de ce framework quand les applications deviennent plus complexes. Datastar, dont je vous parlais dans [la lettre n°7](http://127.0.0.1:8000/lettre-ndeg7-19-janvier-2026.html), napparait pas dans ce sondage, cest dommage, car jaurais bien aimé découvrir les retours dexpérience à son sujet.
* Le taux de satisfaction des utilisateurs de HTMX est en baisse (de 84% en 2016 à 69% en 2025) ; je soupçonne que cela correspond à la découverte des limitations de ce framework quand les applications deviennent plus complexes. Datastar, dont je vous parlais dans [la lettre n°7](https://www.craftletter.fr/lettre-ndeg7-19-janvier-2026.html), napparait pas dans ce sondage, cest dommage, car jaurais bien aimé découvrir les retours dexpérience à son sujet.
* La satisfaction des utilisateurs de React est également en baisse régulière (de 92% en 2016 à 72% en 2025).
* Cypress (Tests End-to-end), autrefois très populaire, a perdu de sa superbe, puisque à peine plus de la moitié des utilisateurs ont envie de le réutiliser.
* À linverse, Playwright (tests End-to-end), Vitest (tests unitaires), Vite (build), Astro (framework pour créer des sites de contenu), Bun (alternative à NodeJs), Hono et Fastify (frameworks Web) sont appréciés par une large majorité de ceux qui les ont utilisés.

View file

@ -0,0 +1,101 @@
Title: Lettre n°11 — 16 février 2026
Date: 2026-02-16 09:00
Category: Newsletter
JsonLD: <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "BlogPosting", "name": "Lettre n°11", "description": "Lettre de veille technologique en développement logiciel", "image": [ "https://www.craftletter.fr/images/craftletter.svg" ], "datePublished": "Mon Feb 16 2026 09:00:00 GMT+0200 (Coordinated Universal Time)", "author": { "@type": "Person", "name": "Pascal Le Merrer", "url": "https://www.linkedin.com/in/pascal-le-merrer/" } } </script>
<img class="logo" alt="Logo Craft Letter" src="{static}/images/craftletter.svg">
## Édito
De temps en temps, une rencontre, une conférence, une méthode ou un article me marque particulièrement, et de façon durable. Parce quil est aligné avec mes valeurs, et quil mapporte un éclairage nouveau. Je mappuie dessus, le cite fréquemment et mets en œuvre tout ou partie de ce quil raconte pendant quelques années, jusquà ce que ce soit tellement intégré que je ny pense même plus, et que cela devienne une évidence —ce qui est un problème, car ce qui est acquis pour moi ne lest pas nécessairement pour les autres. Cela a été le cas, par exemple, il y a 25 ans lors de ma découverte de leXtreme Programming et de lAgilité —létat desprit original, pas la caricature qui sest répandue depuis.
Le premier article mentionné dans cette newsletter fera probablement partie de ces rares élus. Les chiffres quil donne méritent dêtre retenus, et ses conseils devraient être suivis, selon moi.
## La crise de qualité logicielle et de productivité que les exécutifs ne traiteront pas
Si vous ne deviez lire quun seul article cette semaine, cest [celui-là](https://flowchainsensei.wordpress.com/2026/02/04/the-software-quality-and-productivity-crisis-executives-wont-address/) 🇬🇧.
Bob Marshall fait un constat assez accablant, en partant de plusieurs études : la qualité des logiciels sest considérablement dégradée ces dernières années, et les trois quarts des projets de développement logiciel sont des échecs. Ces chiffres nont jamais été aussi hauts. La dette technique est la principale préoccupation de 9 CTO sur 10, et la solution pour la résorber est connue. Malgré ça, la direction des entreprises ny accorde aucun intérêt, tant quune crise néclate pas. Pourquoi ? Parce que la majorité des dirigeants se focalisent sur leurs revenus personnels, et sur le court terme. De même, ils ne sintéressent pas au bien être des développeurs, alors que 83% dentre eux ont fait, ou feront, un burn-out.
Pourtant, lhistoire récente nous montre quun autre monde est possible. Un monde où la qualité est une préoccupation, et où la majorité des projets atteignent leurs objectifs —lun découlant de lautre. Un monde où les développeurs sont heureux de faire leur travail, et ne mettent pas en péril leur santé mentale. Pour revenir à ça, ce que préconise Bob Marshall, cest de prendre en main vous-même la gestion de la dette technique, sans en attendre lautorisation —qui ne viendra pas, ou trop tard. Et de rendre ce travail visible en définissant vos propres indicateurs, qui auront plus de valeur que ceux quon vous impose.
Bob Marshall évoque les risques de cette démarche, mais jajouterais quil ne faut pas en négliger les avantages : se montrer courageux et transparent est un moyen de gagner le respect de vos collègues. Et si vous êtes dans une entreprise qui punit ces qualités au lieu de les récompenser, il est temps den changer.
À lire durgence.
## Laravel vs Symfony
RadWebHosting a établi [une comparaison détaillée des deux frameworks PHP les plus populaires](https://blog.radwebhosting.com/laravel-vs-symfony-a-comprehensive-comparison-of-php-frameworks/) 🇬🇧, Laravel et Symfony, avec des recommandations pour vous guider dans un choix entre les deux.
## Event Storming
L'Event Storming est un type d'atelier inventé par Alberto Brandolini —oui, celui qui a énoncé la [loi de Brandolini](https://fr.wikipedia.org/wiki/Loi_de_Brandolini)🇫🇷, mais ça n'a aucun rapport.
L'Event Storming se décline en différentes variantes, selon l'objectif. Les plus courantes sont :
* **Big Picture Event Storming** : pour explorer collaborativement un domaine métier complexe ;
* **Process Modeling Event Storming** : pour détailler un process métier, du début à la fin (par exemple : de la commande à la livraison d'un produit) ;
* **Software Design Event Storming** : pour enrichir un Process Modeling Event Storming, en y ajoutant des éléments d'architecture logicielle (contextes bornés, agrégats, commandes...).
J'ai animé à plusieurs reprises des ateliers **Process Modeling Event Storming**, avec un public mixte, technique et métier. Mon retour d'expérience, c'est que, s'il est bien préparé, c'est un atelier qui apporte énormément de valeur par rapport au temps consacré —je vous recommande de prévoir deux heures, pas plus. Les participants, même s'ils travaillent sur le logiciel depuis plusieurs années, en apprendront des subtilités qu'ils ignoraient. C'est un outil idéal pour faire découvrir un logiciel existant à de nouveaux venus.
Staffan Palopää explique [comment animer ces ateliers](https://www.qlerify.com/post/event-storming-the-complete-guide) 🇬🇧.
## Moderniser un legacy conséquent sans y perdre ses plumes
Dans cette [première partie](https://blog.octo.com/apprivoiser-un-legacy-consequent-sans-y-perdre-les-plumes) 🇫🇷, Bruno Boucart décrit comment le code legacy sinstalle inexorablement au fil des années, et devient un obstacle pour les développeurs, qui subissent les conséquences de choix organisationnels. La modernisation dun code legacy est souvent perçue comme impossible. Mais la réécriture totale dun logiciel, sans changement des pratiques et de lorganisation, produira une application avec les mêmes défauts que la précédente. En particulier, le management doit adopter la posture du *servant leadership*, cest-à-dire créer les conditions qui permettront aux équipes de réussir, plutôt que de chercher à les contrôler.
Lorganisation dateliers dEvent Storming Big Picture est un moyen dassurer que le nouveau code correspondra aux besoins du métier. Mais cela implique de réorganiser les équipes en fonction des sous-domaines identifiés lors de ces ateliers. Après avoir identifié les sous-domaines, qui sont un découpage du problème, il devient possible de découper la solution technique en contextes bornés (*bounded contexts*). Cet alignement entre lorganisation du code, les responsabilités des équipes, et le métier, est une condition indispensable au succès de la refonte.
Une autre condition réside dans la compréhension du métier par les développeurs. Ce point peut être résolu par lorganisation dateliers. Tout dabord, des ateliers de User Story Mapping, qui permettent didentifier les parcours utilisateurs et prioriser les User Stories. Ensuite, des ateliers dExample Mapping, pour préciser le contenu des User Stories.
La dernière partie de larticle évoque trop rapidement lapproche Team Topologies pour être utile.
Si la différence entre sous-domaines et contextes bornés nest pas claire pour vous, je vous invite à lire [cette explication](https://blog.ancyracademy.fr/posts/bounded-contexts-vs-subdomains/) 🇫🇷.
Si vous souhaitez vous familiariser avec le Domain Driven Design, je vous recommande la lecture de [Learning Domain Driven Design](https://www.oreilly.com/library/view/learning-domain-driven-design/9781098100124/) 🇬🇧 de Vlad Khononov.
Jévoquerai dans la prochaine lettre la suite de larticle de Bruno.
## Découvrir ou approfondir JavaScript et/ou TypeScript
Axel Rauschmayer propose [une série de livres](https://exploringjs.com/) 🇬🇧, à propos de JavaScript et Typescript, dont la version en ligne est gratuite.
## Le but de lintégration continue est déchouer
Je ne suis pas du tout daccord avec le point de vue défendu par lauteur de ce billet de blog, selon lequel [le but de la CI serait déchouer](https://blog.nix-ci.com/post/2026-02-05_the-purpose-of-ci-is-to-fail) 🇬🇧. Selon lui, la valeur ne serait produite que par les erreurs détectées, comme il lexplique après un rappel de ce quil entend par CI (*Continuous Integration*).
Sans surprise, mon point de vue est celui qui est plus communément admis : lobjectif de la CI est de nous assurer que le code que lon sapprête à déployer ne comporte pas de problème évident, et a de bonnes chances de fonctionner en production.
Au passage, jajouterai que si lauteur décrit ce qui est appelé de façon un peu abusive "intégration continue" aujourdhui (par moi aussi), ce nest pas ce que désigne initialement cette expression. Le concept dintégration continue consiste à fusionner tous les jours votre code avec celui de vos collègues, comme son nom lindique. Pour cela, un système de gestion de version tel que Git suffit. Github Actions, Gitlab CI ou un équivalent ne sont pas nécessaires. Le build et les vérifications automatisés exécutés par ces derniers sont un complément très utile de lintégration continue au sens premier. Faire une "vraie" intégration continue élimine la plupart des conflits, ce qui gagne du temps et évite des bugs. Cela nécessite de réaliser en plusieurs fois les changements, plutôt quattendre quune fonctionnalité soit complète avant de fusionner des centaines, voire des milliers de lignes de code. Ce qui peut amener à faire du Trunk Based Development, et améliorer nettement la productivité. Mais cest une autre histoire, dont jaurais certainement loccasion de vous parler plus tard.
## Une conférence au soleil
[Sunnytech](https://sunny-tech.io/) 🇫🇷, la conférence Tech organisée à Montpellier, aura lieu les 2 et 3 juillet 2026. Le CFP est ouvert. La première vague de billets est déjà vendue, mais une nouvelle vague sera mise en vente mi-avril, avant une troisième et dernière vague fin avril.
## Une conférence peut-être un peu moins ensoleillée...
... et encore, ça reste à démontrer ! 😉
Le 9 Juin aura lieu [C:\aen\Tech](https://caen.tech/) 🇫🇷, une conférence Normande qui privilégie les retours dexpérience locaux. Le CFP est ouvert.
## Granian, un serveur HTTP performant pour Python
Cest un euphémisme de dire que les performances ne sont pas le point fort de Python.
[Granian](https://github.com/emmett-framework/granian) est un serveur HTTP écrit en Rust, qui vise à améliorer cet état de fait. Remplacer Guvicorn ou Uvicorn par Granian ne transformera pas votre application en bête de course, car de multiples autres facteurs entrent en jeu : le framework, les algorithmes implémentés, les accès disque et réseau, les requêtes à la base de données... Mais cest une étape, qui peut apporter des changements significatifs par rapport à la simplicité de leffort demandé. Il faudra bien entendu faire des benchmarks pour mesurer limpact réel de ce changement dans votre cas de figure. En effet, dans le pire des cas, il peut savérer négatif. Cela peut arriver si votre code nest pas asynchrone.
Merci à Mikaël qui ma fait découvrir Granian et partagé son retour dexpérience !
## Crystal, un langage pour les humains et les ordinateurs
Lors du Fosdem, cette énorme conférence dédiée à lopen-source, [Johannes Müller a présenté Crystal](https://crystal-lang.org/2026/01/23/crystal-talk-fosdem/) 🇬🇧. Crystal est un langage généraliste, Orienté Objet, inspiré de Ruby. Il en corrige ce que je considère comme des points faibles. Il apporte un typage statique, de linférence de type, de meilleures performances, un déploiement plus facile et une meilleure gestion de la concurrence, tout en conservant la lisibilité et la facilité dutilisation de Ruby. Ce langage sympathique est utilisé en production, comme le montre [cette liste](https://crystal-lang.org/used_in_prod/) qui est probablement incomplète.
Crystal, qui date de 2013, a eu pour slogan jusquà fin 2019 "Fast as C, slick as Ruby" ("*Rapide comme C, Ingénieux comme Ruby*"). Laffirmation selon laquelle Crystal aurait la rapidité du C était exagérée, et cest probablement pourquoi ce slogan a été abandonné.
## Bienfaits du TDD et tests en production
Ola Hast et Asgaut Mjølne Söderbom [expliquent comment leurs équipes obtiennent dexcellents résultats](https://www.infoq.com/news/2026/02/feedback-TDD-production/) 🇬🇧 avec des tests unitaires et des tests dintégration, sans tests end-to-end.
Le TDD et le Pair programming leur permettent décrire un code de qualité. Le Pair et le Mob Programming éliminent le besoin de faire des revues de code, diffusent la connaissance, et augmentent la résilience des équipes.
Puisque les environnements de test ne sont quune approximation de la production, ils les ont quasiment abandonnés au profit du test en production, et déploient de petits incréments, désactivables par des *feature flags* pour réduire les risques.
-----
Voilà, cest tout pour cette semaine.

View file

@ -0,0 +1,104 @@
Title: Lettre n°12 — 23 février 2026
Date: 2026-02-23 09:00
Category: Newsletter
JsonLD: <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "BlogPosting", "name": "Lettre n°12", "description": "Lettre de veille technologique en développement logiciel", "image": [ "https://www.craftletter.fr/images/craftletter.svg" ], "datePublished": "Mon Feb 23 2026 09:00:00 GMT+0200 (Coordinated Universal Time)", "author": { "@type": "Person", "name": "Pascal Le Merrer", "url": "https://www.linkedin.com/in/pascal-le-merrer/" } } </script>
<img class="logo" alt="Logo Craft Letter" src="{static}/images/craftletter.svg">
## Edito
La Craft Letter est récente, et je me pose encore des questions sur son format. Dois-je continuer à faire des résumés détaillés, comme ceux que contient ce numéro à propos de Zig vs C3, ou de la modernisation de code legacy ? Ou des textes dintroduction plus courts sont-ils préférables, comme celui sur AtomVM ? Je vous serais reconnaissant si vous preniez quelques secondes pour répondre à ce [sondage minimaliste](https://framaforms.org/sondage-craft-letter-1771415205)🇫🇷 (il ny a quune question). Les réponses maideront à définir le format des prochaines Craft Letters.
## Limiter les contributions open-source avec Vouch
Mitchell Hashimoto est un développeur prolifique et talentueux. Fondateur de HashiCorp (la société derrière Terraform, Vagrant, Vault...), il est aussi le créateur de Ghostty, un émulateur de terminal que je vous recommande. Son nouveau projet, [Vouch](https://github.com/mitchellh/vouch)🇬🇧, est un outil pour la CI qui limite les contributions aux projets open source (Pull Requests et Issues) à certains comptes de confiance. Les contributeurs ne peuvent pas en certifier dautres : seuls les mainteneurs le peuvent, ce qui garantit quils conservent la main sur leur projet. Par contre, ils peuvent échanger leurs listes de contributeurs de confiance.
Sans surprise, le but est de limiter les contributions de basse qualité générées par IA, comme celles qui ont amené le projet [Curl à fermer son programme de bug bounty](https://intelligence-artificielle.developpez.com/actu/365686/Les-mainteneurs-de-logiciels-libres-sont-noyes-dans-des-rapports-de-bogues-inutiles-rediges-par-l-IA-Ces-systemes-ne-sont-pas-encore-capable-de-comprendre-le-code-estime-un-developpeur/)🇫🇷, ou celles qui [submergent le projet Godot](https://www.pcgamer.com/software/platforms/open-source-game-engine-godot-is-drowning-in-ai-slop-code-contributions-i-dont-know-how-long-we-can-keep-it-up/)🇬🇧.
## Sérialisation rapide en Python
[MsgSpec](https://jcristharif.com/msgspec/)🇬🇧 est une librarie pour (dé)sérialisaliser et valider des données en Python, quelles soient au format JSON, YAML, TOML ou MessagePack. Elle utilise du C pour obtenir des performances élevées.
## Qui, de Zig ou de C3, résout les problèmes du C ?
Dans mes newsletters précédentes, je vous ai parlé de Zig et de C3, deux langages qui se veulent des successeurs du C. Les deux cherchent à en corriger des défauts, chacun à leur manière.
[Cette vidéo](https://www.youtube.com/watch?v=y3tDZACGRAY)🇬🇧 de la chaîne Youtube The Techy Shop explique que ces langages présentent de nombreuses similarités, mais aussi certaines différences, que je peux résumer ainsi :
* C3 est présenté comme une évolution incrémentale de C. Il y introduit de la simplicité, notamment dans la syntaxe et loutillage. Il élimine une partie des pièges de son prédécesseur grâce à des fonctionnalités comme la gestion des erreurs à laide dun type Option.
* Zig se veut une révolution, et est plus ambitieux —au prix dune complexité plus grande que que celle de C3. La preuve en est le système de build, qui nécessite lécriture dun script en Zig, là où C3 utilise un simple fichier JSON. Mais aussi le compilateur, qui, en plus de LLVM, utilise un backend spécifique à Zig, qui réduit significativement les temps de compilation pour le mode debug et les architectures x86_64.
Les deux sont compatibles avec C, mais C3 nutilise pas les fichiers den-tête du C, ce qui implique de déclarer les fonctions C importées. Zig permet dintégrer plus facilement du code C, tandis que C3 soriente plus vers la réécriture du C existant. Aucun des deux noffre déquivalent à linstruction GOTO du C, ce qui implique un travail dadaptation pour porter du code C, même sil existe des moyens de simuler un GOTO.
C3 sappuie sur lécosystème C, là où Zig voudrait le remplacer entièrement.
Malgré leurs objectifs affichés, lexpérience de développement en Zig serait ironiquement plus proche de celle du C que celle de C3, car si elle offre plus de contrôle, elle requiert plus de rigueur en contrepartie. C3 minimise les surprises, au prix de fonctionnalités moins avancées.
Les deux langages évoluent rapidement, ont chacun une communauté passionnée. C3 est toutefois plus récent et moins visible. Zig est plus populaire, et a déjà de belle références, comme Bun ou Ghostty.
Au final Zig et C3 résolvent, de façon différente, les problèmes du C : la complexité de loutillage, les multiples possibilités de se tirer une balle dans le pied. Les deux offrent des performances comparables à du C optimisé, et devraient jouer un rôle important dans la programmation système à lavenir.
Pour en savoir plus sur C3, cette [courte vidéo](https://www.youtube.com/watch?v=yJClpzNxs3s)🇬🇧 explique les différences par rapport à C. Et pour essayer le langage simplement, il y a [un tutoriel](https://learn-c3.org/)🇬🇧 en ligne.
## Adopter Kotlin dans un environnement dominé par Java
Kotlin possède de [nombreux avantages par rapport à Java](https://kotlinlang.org/docs/comparison-to-java.html)🇬🇧. Pour nen citer que quelques-un,
* le code est beaucoup plus concis tout en étant expressif,
* la sécurité est meilleure grâce au contrôle des valeurs nulles par le système de types,
* il ny a pas de conversions implicites,
* les types primitifs de Java sont des objets en Kotlin,
* la gestion de la concurrence utilise des coroutines (plus légères que les threads),
Le tout avec des performances équivalentes.
Quand je parle de concision de Kotlin, je repense toujours à des collègues qui ont réécrit une application mobile Java de 33 000 lignes. La version Kotlin en faisait seulement 17 000. Cest possible notamment parce que le code boilerplate inhérent à Java nexiste pas en Kotlin.
La migration de Java vers Kotlin peut être tout à fait progressive, puisquune application Kotlin peut contenir du code Java, et que linteropérabilité entre les deux langages est excellente. Cette [démonstration](https://www.youtube.com/watch?v=WXVeHvOvSww)🇫🇷 de migration reste intéressante même si elle date un peu, car elle est basée sur un retour dexpérience. Elle montre la simplicité relative dune migration, grâce à loutillage fourni par JetBrains, qui n'est plus limité à leurs IDE, puisqu'il vient juste d'[arriver sous VSCode](https://blog.jetbrains.com/kotlin/2026/02/java-to-kotlin-conversion-comes-to-visual-studio-code/)🇬🇧.
Jetbrains propose [un guide de migration](https://blog.jetbrains.com/kotlin/2025/12/the-ultimate-guide-to-successfully-adopting-kotlin-in-a-java-dominated-environment/)🇬🇧, qui, outre des conseils techniques, fournit des recommandations sur la stratégie à adopter, notamment pour convaincre vos pairs et managers.
## Java : le Valhalla est enfin là !
Près de douze ans après son lancement, [le projet Valhalla est arrivé dans Java 23](https://javaworldmag.com/project-valhalla-value-types-in-production/)🇬🇧, en preview pour linstant.
En Java, tous les objets sont représentés par des zones mémoires allouées sur le tas (*heap*), avec un en-tête, et nécessitent quon y accède via un pointeur —cest masqué, mais ce nest pas sans impact sur les performances. Et ce, même si ces objets ne servent quà encapsuler une ou plusieurs valeurs, comme les coordonnées x et y dun point. Le projet Valhalla résout ce problème.
Il change aussi la façon dont ces données sont stockées dans les collections génériques : au lieu de contenir des pointeurs, elles contiennent directement les valeurs, contiguës en mémoire.
Ces changements améliorent drastiquement les performances, réduisent la fragmentation et la consommation mémoire, ainsi que la fréquence des déclenchements du ramasse-miette. Toutefois, ils ne sont pas applicables à toutes les structures de données.
## Moderniser un legacy conséquent sans y perdre ses plumes - Partie II
Dans la première partie, que jai résumée dans la lettre précédente, Bruno Boucart expliquait la première phase pour réussir la modernisation dune grosse application legacy. Elle consiste à en comprendre le métier, le cartographier, puis aligner lorganisation des équipes et larchitecture sur ce découpage. Dans cette [seconde partie](https://blog.octo.com/apprivoiser-un-legacy-consequent-sans-y-perdre-ses-plumes-partie-ii)🇫🇷, il évoque la transformation du code qui doit suivre.
Après lidentification de contextes bornés (*bounded contexts*) au cours dateliers dEvent Storming, il explique comment remplir pour chacun un [Bounded Context Canvas](https://github.com/ddd-crew/bounded-context-canvas)🇬🇧. Il sagit dun outil de modélisation qui va permettre daffiner la définition du contexte, et de faire émerger le langage ubiquitaire —le vocabulaire partagé entre les équipes métier et techniques, qui est lun des concepts les plus importants du Domain Driven Design (DDD).
Rappel : un contexte borné est une notion du DDD, qui désigne un sous-ensemble de la solution technique.
Bruno rappelle ensuite une série de *design patterns* que lon peut utiliser pour casser les dépendances entre des parties du code. Cest en effet une étape préliminaire indispensable à lécriture des tests qui vont permettre denvisager sereinement des modifications plus importantes.
La réorganisation du code, selon les contextes bornés identifiés précédemment, nest pas suffisante : elle doit saccompagner dune réorganisation de la base de données, de façon à casser là aussi les dépendances entre les parties du système.
Le code refactoré doit refléter fortement le métier : cest ce quon appelle le *deep modeling.* Cela facilite la collaboration entre experts métiers et techniques, mais assure aussi la durabilité du nouveau code, qui tolérera mieux les évolutions futures du métier. Lune des clés pour cela est lutilisation du langage ubiquitaire pour nommer les fonctions, les classes, les variables, etc.
Le contour des classes doit correspondre à celui des concepts métier quelles représente.
Lactivité qui mène à cette modélisation proche du métier porte le nom de Conception Souple (Supple Design) en DDD, et possède ses propres *design patterns*.
Pour améliorer la lisibilité, la testabilité et lévolutivité, le code métier doit être isolé du code technique (accès à la base de données, appels à des APIs…) et de celui des interfaces utilisateurs. Cest le principe de larchitecture hexagonale (également appelée Ports et adaptateurs).
## AtomVM : Erlang, Elixir ou Gleam sur un microcontrôleur
[Davide Bettio présente AtomVM](https://fosdem.org/2026/schedule/event/YP97YR-atomvm_elixir_erlang_and_gleam_on_microcontrollers/)🇬🇧, une machine virtuelle capable dexécuter des projets écrits en Erlang, Elixir ou Gleam sur un microcontrôleur comme un ESP32, un Raspberry PI Pico ou un ESP32. Il en explique aussi lintérêt : tolérance aux erreurs, gestion de la mémoire, de lasynchronisme, des valeurs binaires...
## Principe de responsabilité unique en programmation fonctionnelle
Christian Ekrem montre comment le Principe de Responsabilité Unique (le S de SOLID, *Single Responsibility Principle*) sapplique de façon naturelle dans certains langages fonctionnels. En nautorisant que lécriture de fonctions pures, [Elm interdit des antipatterns](https://cekrem.github.io/posts/solid-in-fp-single-responsibility/)🇬🇧 que lon peut implémenter en TypeScript.
## Another World, une belle leçon d'architecture logicielle
Olivier Poncet raconte dans une conférence passionnante [comment un jeu culte des années 90 a été conçu](https://www.youtube.com/watch?v=OyJScQqUAsA)🇫🇷. Il explique clairement des détails de conception d'une grande ingéniosité, utilisés pour compenser les faibles ressources du matériel de l'époque.
-----
Voilà, cest tout pour cette semaine.
Avant de vaquer à dautres activités, pensez à remplir le [sondage](https://framaforms.org/sondage-craft-letter-1771415205)🇫🇷 si ce nest pas encore fait !

View file

@ -1,12 +1,15 @@
Title: Accueil
Date: 2026-02-09 09:00
Date: 2026-02-23 09:00
URL:
save_as: index.html
Category: Home
JsonLD: { "@context": "https://schema.org", "@type": "WebPage", "name": "Accueil", "description": "Lettre de veille technologique en développement logiciel", "image": [ "https://www.craftletter.fr/images/craftletter.svg" ], "datePublished": "Mon Feb 09 2026 09:00:00 GMT+0200 (Coordinated Universal Time)", "author": { "@type": "Person", "name": "Pascal Le Merrer", "url": "https://www.linkedin.com/in/pascal-le-merrer/" } }
JsonLD: { "@context": "https://schema.org", "@type": "WebPage", "name": "Accueil", "description": "Lettre de veille technologique en développement logiciel", "image": [ "https://www.craftletter.fr/images/craftletter.svg" ], "datePublished": "Mon Feb 23 2026 09:00:00 GMT+0200 (Coordinated Universal Time)", "author": { "@type": "Person", "name": "Pascal Le Merrer", "url": "https://www.linkedin.com/in/pascal-le-merrer/" } }
<img class="logo" alt="Logo Craft Letter" src="{static}/images/craftletter.svg">
# La [lettre n°12]({filename}/newsletter/craft-letter-12.md) est parue !
La Craft Letter est une newsletter hebdomadaire dans laquelle je partage des articles
issues de ma veille technologique. Vous y trouverez des articles relatifs au développement logiciel d'une façon générale, qu'il soit front-end, back-end ou autre. Mais aussi des articles consacrés à l'architecture logicielle, la méthodologie, les outils, des projets open source, des conférences...
@ -34,6 +37,8 @@ Pour savoir qui je suis, ou pourquoi j'écris cette lettre, je vous invite à vo
# Archives
* [Lettre n°12]({filename}/newsletter/craft-letter-12.md)
* [Lettre n°11]({filename}/newsletter/craft-letter-11.md)
* [Lettre n°10]({filename}/newsletter/craft-letter-10.md)
* [Lettre n°9]({filename}/newsletter/craft-letter-9.md)
* [Lettre n°8]({filename}/newsletter/craft-letter-8.md)

View file

@ -4,7 +4,8 @@
"python313Packages.pip@latest",
"ruby@latest",
"hunspellDicts.fr-moderne@latest",
"hunspell@latest"
"hunspell@latest",
"stork@latest"
],
"shell": {
"init_hook": [

View file

@ -301,6 +301,54 @@
"store_path": "/nix/store/gphbrsb5wd49qwg9bxby194wwm47awks-ruby-3.4.8"
}
}
},
"stork@latest": {
"last_modified": "2026-01-23T17:20:52Z",
"resolved": "github:NixOS/nixpkgs/a1bab9e494f5f4939442a57a58d0449a109593fe#stork",
"source": "devbox-search",
"version": "1.6.0",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/ck1rmp4lh2xis1sadlsbg7aq0105wl3b-stork-1.6.0",
"default": true
}
],
"store_path": "/nix/store/ck1rmp4lh2xis1sadlsbg7aq0105wl3b-stork-1.6.0"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/84q9aw2y7fz9ssg391ijspwpa833h2yn-stork-1.6.0",
"default": true
}
],
"store_path": "/nix/store/84q9aw2y7fz9ssg391ijspwpa833h2yn-stork-1.6.0"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/0pdqh1jl67qyqyhh51iszvp1g51i5vy1-stork-1.6.0",
"default": true
}
],
"store_path": "/nix/store/0pdqh1jl67qyqyhh51iszvp1g51i5vy1-stork-1.6.0"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/0kvfmzsm9fsnv128lwlyc08ak8amrgm8-stork-1.6.0",
"default": true
}
],
"store_path": "/nix/store/0kvfmzsm9fsnv128lwlyc08ak8amrgm8-stork-1.6.0"
}
}
}
}
}

View file

@ -63,6 +63,11 @@ new_content = set_publication_date_for_pelican(content, next_monday)
new_content = set_publication_date_for_seo(new_content, next_monday)
link = (
f"[lettre n°{args.number}]({{filename}}/newsletter/craft-letter-{args.number}.md)"
)
new_content = new_content.replace("{LINK}", link)
for i in reversed(range(args.number)):
link = f"* [Lettre n°{i + 1}]({{filename}}/newsletter/craft-letter-{i + 1}.md)\n"
new_content += link

View file

@ -7,8 +7,11 @@ JsonLD: { "@context": "https://schema.org", "@type": "WebPage", "name": "Accueil
<img class="logo" alt="Logo Craft Letter" src="{static}/images/craftletter.svg">
# La {LINK} est parue !
La Craft Letter est une newsletter hebdomadaire dans laquelle je partage des articles
issues de ma veille technologique. Vous y trouverez des articles relatifs au développement logiciel d'une façon générale, qu'il soit front-end, back-end ou autre. Mais aussi des articles consacrés à l'architecture logicielle, la méthodologie, les outils, des projets open source, des conférences...
issus de ma veille technologique. Vous y trouverez des articles relatifs au développement logiciel d'une façon générale, qu'il soit front-end, back-end ou autre. Mais aussi des articles consacrés à l'architecture logicielle, la méthodologie, les outils, des projets open source, des conférences...
Pour savoir qui je suis, ou pourquoi j'écris cette lettre, je vous invite à vous lire l'édito du [premier numéro]({filename}/newsletter/craft-letter-1.md).

View file

@ -1,4 +1,4 @@
Title: Lettre n°{LETTER_NUMBER} - {DATE}
Title: Lettre n°{LETTER_NUMBER} {DATE}
Date: {DATE_DIGITS} 09:00
Category: Newsletter
JsonLD: <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "BlogPosting", "name": "Lettre n°{LETTER_NUMBER}", "description": "Lettre de veille technologique en développement logiciel", "image": [ "https://www.craftletter.fr/images/craftletter.svg" ], "datePublished": "{DATE_UTC} 09:00:00 GMT+0200 (Coordinated Universal Time)", "author": { "@type": "Person", "name": "Pascal Le Merrer", "url": "https://www.linkedin.com/in/pascal-le-merrer/" } } </script>

View file

@ -0,0 +1,17 @@
# Contributors
* [Nevan Scott](https://github.com/nevanscott/Mockingbird) (original author)
* [wrl](http://ghttps://github.com/guikcdithub.com/wrl) (port to pelican, pelican-mockingbird)
* [Jody Frankowski](http://github.com/jody-frankowski) (Blue Penguin)
* [Grimbox](https://github.com/Grimbox)
* [ix5](https://github.com/ix5)
* [dn0](https://github.com/dn0)
* [anhtuann](https://github.com/anhtuann)
* [aperep](https://github.com/aperep)
* [iranzo](https://github.com/iranzo)
* [thetlk](https://github.com/thetlk)
* [SnorlaxYum](https://github.com/SnorlaxYum)
* [guikcd](https://github.com/guikcd)
* [jorgesumle](https://github.com/jorgesumle)
* [crxxn](https://github.com/crxxn)
* [Pascal Le Merrer](https://www.craftletter.fr)

View file

@ -0,0 +1,53 @@
![screenshot](screenshot.png)
# Blue Penguin for pelican
A simple theme for pelican. Solarized pygments. Feeds support.
## Settings
```python
# all the following settings are *optional*
# HTML metadata
SITEDESCRIPTION = ''
# all defaults to True.
DISPLAY_HEADER = True
DISPLAY_FOOTER = True
DISPLAY_HOME = True
DISPLAY_MENU = True
# provided as examples, they make clean urls. used by MENU_INTERNAL_PAGES.
TAGS_URL = 'tags'
TAGS_SAVE_AS = 'tags/index.html'
AUTHORS_URL = 'authors'
AUTHORS_SAVE_AS = 'authors/index.html'
CATEGORIES_URL = 'categories'
CATEGORIES_SAVE_AS = 'categories/index.html'
ARCHIVES_URL = 'archives'
ARCHIVES_SAVE_AS = 'archives/index.html'
# use those if you want pelican standard pages to appear in your menu
MENU_INTERNAL_PAGES = (
('Tags', TAGS_URL, TAGS_SAVE_AS),
('Authors', AUTHORS_URL, AUTHORS_SAVE_AS),
('Categories', CATEGORIES_URL, CATEGORIES_SAVE_AS),
('Archives', ARCHIVES_URL, ARCHIVES_SAVE_AS),
)
# additional menu items
MENUITEMS = (
('GitHub', 'https://github.com/'),
('Linux Kernel', 'https://www.kernel.org/'),
)
```
## How to contribute
Contributions are very welcome. Keep in mind that this theme goal is to be
minimalistic/simple. Contributions will be accepted through Github Pull
Requests. If you dont have a Github account you can suggest me your
changes by email (which you can find on my github profile).
## Contributors
See [CONTRIBUTORS.md](CONTRIBUTORS.md).
## License
Public domain.

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 KiB

View file

@ -0,0 +1,4 @@
* { background: #fff; }
body { font-family: georgia, times, serif; color: black; }
blockquote { font-style: italic; color: black; }
a:link, a:visited { border-bottom-width: 1px; border-bottom-style: solid; }

View file

@ -0,0 +1,87 @@
/* Solarized Dark
For use with Jekyll and Pygments
https://ethanschoonover.com/solarized
SOLARIZED HEX ROLE
--------- -------- ------------------------------------------
base03 #002b36 background
base01 #586e75 comments / secondary content
base1 #93a1a1 body text / default code / primary content
orange #cb4b16 constants
red #dc322f regex, special keywords
blue #268bd2 reserved keywords
cyan #2aa198 strings, numbers
green #859900 operators, other keywords
*/
.highlight { background-color: #002b36; color: #93a1a1 }
.highlight .c { color: #586e75 } /* Comment */
.highlight .err { color: #93a1a1 } /* Error */
.highlight .g { color: #93a1a1 } /* Generic */
.highlight .k { color: #859900 } /* Keyword */
.highlight .l { color: #93a1a1 } /* Literal */
.highlight .n { color: #93a1a1 } /* Name */
.highlight .o { color: #859900 } /* Operator */
.highlight .x { color: #cb4b16 } /* Other */
.highlight .p { color: #93a1a1 } /* Punctuation */
.highlight .cm { color: #586e75 } /* Comment.Multiline */
.highlight .cp { color: #859900 } /* Comment.Preproc */
.highlight .c1 { color: #586e75 } /* Comment.Single */
.highlight .cs { color: #859900 } /* Comment.Special */
.highlight .gd { color: #2aa198 } /* Generic.Deleted */
.highlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */
.highlight .gr { color: #dc322f } /* Generic.Error */
.highlight .gh { color: #cb4b16 } /* Generic.Heading */
.highlight .gi { color: #859900 } /* Generic.Inserted */
.highlight .go { color: #93a1a1 } /* Generic.Output */
.highlight .gp { color: #93a1a1 } /* Generic.Prompt */
.highlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #cb4b16 } /* Generic.Subheading */
.highlight .gt { color: #93a1a1 } /* Generic.Traceback */
.highlight .kc { color: #cb4b16 } /* Keyword.Constant */
.highlight .kd { color: #268bd2 } /* Keyword.Declaration */
.highlight .kn { color: #859900 } /* Keyword.Namespace */
.highlight .kp { color: #859900 } /* Keyword.Pseudo */
.highlight .kr { color: #268bd2 } /* Keyword.Reserved */
.highlight .kt { color: #dc322f } /* Keyword.Type */
.highlight .ld { color: #93a1a1 } /* Literal.Date */
.highlight .m { color: #2aa198 } /* Literal.Number */
.highlight .s { color: #2aa198 } /* Literal.String */
.highlight .na { color: #93a1a1 } /* Name.Attribute */
.highlight .nb { color: #B58900 } /* Name.Builtin */
.highlight .nc { color: #268bd2 } /* Name.Class */
.highlight .no { color: #cb4b16 } /* Name.Constant */
.highlight .nd { color: #268bd2 } /* Name.Decorator */
.highlight .ni { color: #cb4b16 } /* Name.Entity */
.highlight .ne { color: #cb4b16 } /* Name.Exception */
.highlight .nf { color: #268bd2 } /* Name.Function */
.highlight .nl { color: #93a1a1 } /* Name.Label */
.highlight .nn { color: #93a1a1 } /* Name.Namespace */
.highlight .nx { color: #93a1a1 } /* Name.Other */
.highlight .py { color: #93a1a1 } /* Name.Property */
.highlight .nt { color: #268bd2 } /* Name.Tag */
.highlight .nv { color: #268bd2 } /* Name.Variable */
.highlight .ow { color: #859900 } /* Operator.Word */
.highlight .w { color: #93a1a1 } /* Text.Whitespace */
.highlight .mf { color: #2aa198 } /* Literal.Number.Float */
.highlight .mh { color: #2aa198 } /* Literal.Number.Hex */
.highlight .mi { color: #2aa198 } /* Literal.Number.Integer */
.highlight .mo { color: #2aa198 } /* Literal.Number.Oct */
.highlight .sb { color: #586e75 } /* Literal.String.Backtick */
.highlight .sc { color: #2aa198 } /* Literal.String.Char */
.highlight .sd { color: #93a1a1 } /* Literal.String.Doc */
.highlight .s2 { color: #2aa198 } /* Literal.String.Double */
.highlight .se { color: #cb4b16 } /* Literal.String.Escape */
.highlight .sh { color: #93a1a1 } /* Literal.String.Heredoc */
.highlight .si { color: #2aa198 } /* Literal.String.Interpol */
.highlight .sx { color: #2aa198 } /* Literal.String.Other */
.highlight .sr { color: #dc322f } /* Literal.String.Regex */
.highlight .s1 { color: #2aa198 } /* Literal.String.Single */
.highlight .ss { color: #2aa198 } /* Literal.String.Symbol */
.highlight .bp { color: #268bd2 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #268bd2 } /* Name.Variable.Class */
.highlight .vg { color: #268bd2 } /* Name.Variable.Global */
.highlight .vi { color: #268bd2 } /* Name.Variable.Instance */
.highlight .il { color: #2aa198 } /* Literal.Number.Integer.Long */

View file

@ -0,0 +1,495 @@
/* https://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
/* Mockingbird Theme by Nevan Scott nevanscott.com */
/* Modified by Jody Frankowski */
/* Modified by ix5 */
/* Modified by Pascal Le Merrer */
:root {
--link-color: #801515;
--link-background-color: #abdae7;
}
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
em {
font-style: italic;
}
strong {
font-weight: bold;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
font-family: sans-serif;
font-size: 16px;
line-height: 1.5em;
color: #444;
}
header,
#wrapper {
padding: 0 10px;
max-width: 910px;
margin: auto;
}
#wrapper {
min-width: 500px;
}
a {
box-shadow: inset 0 0 0 0 var(--link-background-color);
color: var(--link-color);
padding: 0 0.25rem;
margin: 0 -0.25rem;
transition:
color 0.3s ease-in-out,
box-shadow 0.3s ease-in-out;
}
a:hover {
box-shadow: inset 800px 0 0 0 var(--link-background-color);
}
ul {
list-style: outside disc;
}
ol {
list-style: outside decimal;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: sans-serif;
font-weight: bold;
}
h1,
h2,
h3 {
line-height: 1em;
margin: 1em 0;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.7em;
}
h3 {
font-size: 1.5em;
}
img,
p,
.post > .highlight,
.highlighttable,
h4,
h5,
h6 {
margin-top: 1.2em;
}
blockquote {
margin: 1.5em 1.5em 1.5em 0.75em;
padding-left: 0.75em;
border-left: 1px solid #eee;
}
.date {
color: #ccc;
float: left;
clear: both;
width: 130px;
font-size: 1.5em;
line-height: 1em;
margin: 0 20px 1em 0;
}
.info {
margin-top: 1.3em;
font-family: sans-serif;
text-align: right;
color: #bbb;
}
.info a {
color: inherit;
}
.info a.tags {
background: #ccc;
color: #fff;
display: inline-block;
padding: 0 0.3em;
border: 1px transparent solid;
border-radius: 5px;
margin: 0 0 0.3em 0;
}
.info a.tags:hover {
background: inherit;
color: inherit;
}
.info a.tags.selected {
border: 1px #999 solid;
}
.post {
margin: 0 0 4.5em 150px;
}
.post.archives {
margin-bottom: 1.5em;
margin-left: 160px;
}
.post p {
text-align: justify;
}
.page {
margin: 0 90px;
}
.highlight {
border-radius: 3px;
}
.code > .highlight {
border-radius: 0px 3px 3px 0px;
}
.linenos {
border-radius: 3px 0px 0px 3px;
background-color: #073642;
border-right: 1px solid #00232c;
color: #586e75;
text-shadow: 0px -1px #021014;
}
td.code {
width: 100%;
max-width: 100px;
}
.linenos a {
color: #586e75;
}
.logo {
max-width: 100%;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 6rem;
margin-bottom: 6rem;
}
/*sub and sup stolen from Twitter bootstrap.*/
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
.post pre,
.page pre {
padding: 0.8em;
font-size: 12px;
font-family: Monospace;
line-height: 1.1em;
overflow: auto;
}
form.inline_edit {
clear: both;
margin: 4.5em 0;
background-color: #ddd;
color: #000;
padding: 20px;
border-radius: 5px;
}
.inline_edit .sub {
color: #888;
white-space: nowrap;
}
.inline_edit label {
float: left;
clear: both;
width: 140px;
margin-right: 20px;
}
.inline_edit .buttons {
display: block;
text-align: right;
}
nav {
display: flex;
flex-direction: row;
gap: 30px;
}
nav ul {
list-style: none;
padding: 0;
}
nav a {
color: var(--link-color);
}
nav a:hover {
color: var(--link-color);
text-decoration: underline;
background-color: #d3d3d3;
}
nav li.selected a {
background-color: var(--link-background-color);
color: var(--link-color);
}
header .header_box {
padding-top: 4.5em;
}
header h1 {
font-size: 1.5em;
line-height: 1em;
margin: 0;
}
header h2 {
font-size: 1em;
margin: 0.3em 0;
color: #ddd;
}
#content {
margin-top: 3em;
}
.pages {
font-family: sans-serif;
line-height: 2.5em;
margin: 4.5em 0 3em;
background-color: #f9f9f9;
color: #444;
border-radius: 5px;
}
.pages a.next_page {
float: right;
width: 140px;
text-align: center;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
background-color: #eee;
}
.pages a.prev_page {
float: left;
width: 140px;
text-align: center;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
background-color: #eee;
}
.pages a {
color: inherit;
border: none;
}
.pages a:hover {
background-color: #ddd;
}
.pages span {
display: block;
margin: 0 160px;
text-align: center;
}
code {
background-color: #f9f2f4;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
box-sizing: border-box;
color: #c7254e;
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
font-size: 12.6px;
line-height: 18px;
padding-bottom: 2px;
padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
white-space: nowrap;
}
footer {
font-family: sans-serif;
line-height: 2.5em;
text-align: center;
color: #bbb;
margin: 3em 0;
border: 1px solid #eee;
border-radius: 5px;
}
footer p {
margin: 0;
}
.right {
float: right;
}
.clear {
clear: both;
}
@media (max-width: 600px) {
header,
#wrapper {
max-width: 500px;
min-width: 300px;
}
.page,
.post {
margin: 0px;
}
}
hr {
color: #cccccc;
margin-top: 40px;
margin-bottom: 40px;
}

View file

@ -0,0 +1,11 @@
{% if GOOGLE_ANALYTICS %}
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("{{GOOGLE_ANALYTICS}}");
pageTracker._trackPageview();
} catch(err) {}</script>
{% endif %}

View file

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}{{ SITENAME }} | Archives{% endblock %}
{% block content %}
<h1>Archives</h1>
{# based on https://stackoverflow.com/questions/12764291/jinja2-group-by-month-year #}
{% for year, year_group in dates|groupby('date.year')|reverse %}
{% for month, month_group in year_group|groupby('date.month')|reverse %}
<h4 class="date">{{ (month_group|first).date|strftime('%b %Y') }}</h4>
<div class="post archives">
<ul>
{% for article in month_group %}
<li><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></li>
{% endfor %}
</ul>
</div>
{% endfor %}
{% endfor %}
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% block head %}
{{ super() }}
{% if article.tags %}
<meta name="keywords" content="{{ article.tags|join(",") }}" />
{% endif %}
{% if article.description %}
<meta name="description" content="{{ article.description }}" />
{% endif %}
<script type="application/ld+json">
{{ article.jsonld }}
</script>
{% endblock %}
{% block title %}{{ SITENAME }} | {{ article.title }}{% endblock %}
{% block content %}
{% include "article_stub.html" %}
{% endblock %}

View file

@ -0,0 +1,37 @@
{% if not articles_page or first_article_of_day %}
<h4 class="date">{{ article.date.strftime("%d %b %Y") }}</h4>
{% endif %}
<article class="post">
{% if article.title %}
<h2 class="title">
<a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permanent Link to &quot;{{ article.title }}&quot;">{{ article.title }}</a>
</h2>
{% endif %}
{% if not articles_page %}
{% include "translations.html" %}
{% endif %}
{{ article.content }}
<div class="clear"></div>
<div class="info">
<a href="{{ SITEURL }}/{{ article.url }}">Publié à {{ article.date.strftime("%H:%M") }}</a>
{% if article.category.name != "misc" %}
&nbsp;&middot;&nbsp;<a href="{{ SITEURL }}/{{ article.category.url }}" rel="tag">{{ article.category }}</a>
{% endif %}
{% if article.tags %}
&nbsp;&middot;
{% for t in article.tags %}
&nbsp;<a href="{{ SITEURL }}/{{ t.url }}" class="tags{% if tag and tag.name == t.name %} selected{% endif %}">{{ t }}</a>
{% endfor %}
{% endif %}
</div>
{% if articles_page and DISQUS_SITENAME %}
<a href="{{ SITEURL }}/{{ article.url }}#disqus_thread">Click to read and post comments</a>
{% else %}
{% include "disqus.html" %}
{% endif %}
</article>

View file

@ -0,0 +1,7 @@
{% extends "index.html" %}
{% block title %}{{ SITENAME }} | Articles by {{ author }}{% endblock %}
{% block ephemeral_nav %}
{{ ephemeral_nav_link(author, output_file, True) }}
{% endblock %}

View file

@ -0,0 +1,123 @@
{% macro ephemeral_nav_link(what, where, selected=False) -%}
<li class="ephemeral{% if selected %} selected{% endif %}"><a href="{{ SITEURL }}/{{ where }}">{{what}}</a></li>
{%- endmacro -%}
<!DOCTYPE html>
<html lang="{{ DEFAULT_LANG }}">
<head>
{% block head %}
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{% block title %}{{ SITENAME }}{% endblock title %}</title>
{# favicon #}
<link rel="shortcut icon" type="image/png" href="{{ SITEURL }}/favicon.png">
<link rel="shortcut icon" type="image/x-icon" href="{{ SITEURL }}/favicon.ico">
{% if FEED_ALL_ATOM %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Full Atom Feed" />
{% endif %}
{% if FEED_ALL_RSS %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Full RSS Feed" />
{% endif %}
{% if FEED_ATOM %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Atom Feed" />
{% endif %}
{% if FEED_RSS %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} RSS Feed" />
{% endif %}
{% if CATEGORY_FEED_ATOM and category %}
<link href="{{ FEED_DOMAIN }}/{{ CATEGORY_FEED_ATOM.format(slug=category.slug) }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Categories Atom Feed" />
{% endif %}
{% if CATEGORY_FEED_RSS and category %}
<link href="{{ FEED_DOMAIN }}/{{ CATEGORY_FEED_RSS.format(slug=category.slug) }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Categories RSS Feed" />
{% endif %}
{% if TAG_FEED_ATOM and tag %}
<link href="{{ FEED_DOMAIN }}/{{ TAG_FEED_ATOM.format(slug=tag.slug) }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Tags Atom Feed" />
{% endif %}
{% if TAG_FEED_RSS and tag %}
<link href="{{ FEED_DOMAIN }}/{{ TAG_FEED_RSS.format(slug=tag.slug) }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Tags RSS Feed" />
{% endif %}
<link rel="stylesheet" href="{{ SITEURL }}/theme/css/screen.css" type="text/css" />
<link rel="stylesheet" href="{{ SITEURL }}/theme/css/pygments.css" type="text/css" />
<link rel="stylesheet" href="{{ SITEURL }}/theme/css/print.css" type="text/css" media="print" />
<meta name="generator" content="Pelican" />
<meta name="description" content="{{ SITEDESCRIPTION }}" />
<meta name="author" content="{{ AUTHOR }}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ SITEURL }}" />
<meta property="og:title" content="{{ SITENAME }}" />
<meta property="og:image" content="{{ SITEURL }}/images/LogoCraftLetter-800px.png" />
<meta property="og:image:width" content="800" />
<meta property="og:image:height" content="222" />
<meta property="og:image:type" content="image/png" />
<meta property="og:description" content="{{ SITEDESCRIPTION }}" />
<link rel="stylesheet" href="https://files.stork-search.net/basic.css" />
{% endblock head %}
</head>
<body>
{% if DISPLAY_HEADER or DISPLAY_HEADER is not defined %}
<header>
{% if DISPLAY_MENU or DISPLAY_MENU is not defined %}
<nav>
<ul>
{% block ephemeral_nav %}{% endblock %}
<!-- {% if DISPLAY_HOME or DISPLAY_HOME is not defined %} -->
<!-- <li{% if output_file == "index.html" %} class="selected"{% endif %}><a href="{{ SITEURL }}/">Home</a></li> -->
<!-- {% endif %} -->
{% if DISPLAY_PAGES_ON_MENU %}
{% for p in pages %}
<li{% if p == page %} class="selected"{% endif %}><a href="{{ SITEURL }}/{{ p.url }}">{{ p.title }}</a></li>
{% endfor %}
{% endif %}
{% for title, link in MENUITEMS %}
<li><a href="{{ link }}">{{ title }}</a></li>
{% endfor %}
{% for name, link, file in MENU_INTERNAL_PAGES %}
<li{% if output_file == file %} class="selected"{% endif %}><a href="{{ SITEURL }}/{{ link }}">{{ name }}</a></li>
{% endfor %}
</ul>
<ul>
<search>Rechercher : <input data-stork="sitesearch" />
<div data-stork="sitesearch-output"></div>
</search>
</ul>
</nav>
{% endif %}
<div class="header_box">
<h1><a href="{{ SITEURL }}/">{{ SITENAME }}</a></h1>
{% if SITESUBTITLE %}
<h2>{{ SITESUBTITLE }}</h2>
{% endif %}
</div>
</header>
{% endif %}
<div id="wrapper">
<div id="content">
<main>
{%- block content -%}{%- endblock %}
<!-- </main> -->
{% if DISPLAY_FOOTER or DISPLAY_FOOTER is not defined %}
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
&middot;
Propulsé par <a href="https://getpelican.com">Pelican</a>
{% if FEED_ALL_RSS %}
&middot;
<a href="{{ SITEURL }}/{{ FEED_ALL_RSS }}" rel="alternate">Flux RSS</a>
{% endif %}
<p>
<a href="https://www.craftletter.fr">Craft Letter</a> © 2025 - 2026 par <a href="https://www.linkedin.com/in/pascal-le-merrer/">Pascal Le Merrer</a> est sous licence <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">CC BY-NC-ND 4.0</a><div><img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="CC" style="max-width: 1em;max-height:1em;margin-left: .2em;"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="BY" style="max-width: 1em;max-height:1em;margin-left: .2em;"><img src="https://mirrors.creativecommons.org/presskit/icons/nc.svg" alt="NC" style="max-width: 1em;max-height:1em;margin-left: .2em;"><img src="https://mirrors.creativecommons.org/presskit/icons/nd.svg" alt="ND" style="max-width: 1em;max-height:1em;margin-left: .2em;"></div>
<p/>
</footer>
{% endif %}
</div>
<div class="clear"></div>
</div>
<script src="https://files.stork-search.net/releases/v1.5.0/stork.js"></script>
<script>
stork.register("sitesearch", "{{ SITEURL }}/search-index.st");
</script>
</body>
</html>

View file

@ -0,0 +1,6 @@
{% extends "index.html" %}
{% block title %}{{ SITENAME }} | articles in the "{{ category }}" category{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %}
{% block ephemeral_nav %}
{{ ephemeral_nav_link(category, output_file, True) }}
{% endblock %}

View file

@ -0,0 +1,12 @@
{% if DISQUS_SITENAME %}
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_shortname = '{{ DISQUS_SITENAME }}';
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>
{% endif %}

View file

@ -0,0 +1,17 @@
{% extends "base.html" %}
{% block title %}{{ SITENAME }}{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %}
{% block content %}
{% set date = None %}
{% for article in articles_page.object_list %}
{% if date != article.date.date() %}
{% set first_article_of_day = True %}
{% else %}
{% set first_article_of_day = False %}
{% endif %}
{% set date = article.date.date() %}
{% include "article_stub.html" %}
{% endfor %}
{% include "pagination.html" %}
{% endblock %}

View file

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block head %}
{{ super() }}
<script type="application/ld+json">
{{ page.jsonld }}
</script>
{% endblock %}
{% block title %}{{ SITENAME }} | {{ page.title }}{% endblock %}
{% block content %}
<div class="page">
<h1>{{ page.title }}</h1>
{{ page.content }}
</div>
{% endblock %}

View file

@ -0,0 +1,38 @@
{# Use PAGINATION_PATTERNS or pagination may break #}
{% if DEFAULT_PAGINATION and (articles_page.has_previous() or articles_page.has_next()) %}
<div class="clear"></div>
<div class="pages">
{% if PAGINATION_PATTERNS[0][0] != 0 %}
{%- if articles_page.has_previous() %}
{% if articles_page.previous_page_number() == 1 %}
<a href="{{ SITEURL }}/" class="prev_page">&larr;&nbsp;Previous</a>
{%- else %}
<a href="{{ SITEURL }}/page/{{ articles_page.previous_page_number() }}" class="prev_page">&larr;&nbsp;Previous</a>
{%- endif %}
{%- endif %}
{%- if articles_page.has_next() %}
<a href="{{ SITEURL }}/page/{{ articles_page.next_page_number() }}" class="next_page">Next&nbsp;&rarr;</a>
{%- endif %}
{% else %}
{%- if articles_page.has_previous() %}
{% if articles_page.previous_page_number() == 1 %}
<a href="{{ SITEURL }}/{{ page_name }}.html" class="prev_page">&larr;&nbsp;Previous</a>
{%- else %}
<a href="{{ SITEURL }}/{{ page_name }}{{ articles_page.previous_page_number() }}.html" class="prev_page">&larr;&nbsp;Previous</a>
{%- endif %}
{%- endif %}
{%- if articles_page.has_next() %}
<a href="{{ SITEURL }}/{{ page_name }}{{ articles_page.next_page_number() }}.html" class="next_page">Next&nbsp;&rarr;</a>
{%- endif %}
{% endif %}
<span>Page {{ articles_page.number }} of {{ articles_paginator.num_pages }}</span>
</div>
{% endif %}

View file

@ -0,0 +1,5 @@
{% extends "index.html" %}
{% block title %}{{ SITENAME }} | articles tagged "{{ tag }}"{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %}
{% block ephemeral_nav %}
{{ ephemeral_nav_link(tag, output_file, True) }}
{% endblock %}

View file

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<ul>
{% for tag, articles in tags %}
<li><a href="{{ SITEURL }}/{{ tag.url }}">{{ tag }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View file

@ -0,0 +1,6 @@
{% if article.translations %}
Translations:
{% for translation in article.translations %}
<a href="{{ SITEURL }}/{{ translation.url }}">{{ translation.lang }}</a>
{% endfor %}
{% endif %}