Added fonts
All checks were successful
Tests et déploiement / validate (push) Successful in 2m54s
Tests et déploiement / deploy (push) Successful in 41s

Added license file and updated README
This commit is contained in:
2026-10-11 12:29:04 +02:00
parent e53ae6f8be
commit 48445ab328
11 changed files with 1560 additions and 482 deletions

682
LICENSE.md Normal file
View File

@@ -0,0 +1,682 @@
Pardis source code
Copyright (C) 2026 EI Adrien Weiskopff pardis@clawd.fr
This program is licensed under the GNU General Public License, version 3 only.
The full license text follows.
---
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -1,132 +0,0 @@
# Validation avant production — Pardis
## Vérifications exécutées dans cet environnement
- Dernière validation locale : 10 octobre 2026, avec `npm run validate` et la
suite Playwright utilisant le Chromium Headless Shell installé via
`PARDIS_CHROME_PATH`. Ces résultats ne remplacent pas une recette sur téléphones
réels ni le résultat du workflow CI du commit publié.
- 272 tests unitaires et IndexedDB simulée passent (15 fichiers).
- `npm run check` : aucune erreur ni avertissement.
- Build PWA complet : `sw.js`, Workbox, manifeste et précache générés.
- L’importeur de la version précédente, extrait du commit avant l’évolution du format, refuse réellement
un export v2 avec son message de mise à jour (sans importer les pauses comme travail).
- Cas couverts : 8h30 de travail / 30 minutes de pause, récapitulatif/copier et
suggestions sans pause, reprise continue et discontinue à 18h, reprise avec
dernier repas/pause, deux appels concurrents, aucune pause vide, refus de reprise
passée, annulation transactionnelle sur erreur, repas automatique pendant une
pause, clôture pendant une pause, ajout/correction/suppression sur journée close,
import v1 et aller-retour v2, préservation des intervalles vides, écriture de
réglages contre leur dernière valeur persistée et échec d’écriture.
Commande reproductible : `npm ci` puis `npm run validate`.
La CI Gitea exécute déjà tests, vérification Svelte et build avant déploiement.
## Parcours navigateur automatisés
Seize tests Playwright exécutent le build PWA réel dans Chrome, avec des profils
éphémères et des fixtures synthétiques à 320 px. Ils couvrent consultation et
correction, copie, statistiques, date et Retour après rechargement, focus et
Échap (y compris les panneaux imbriqués), annulation et double appui, deux onglets,
les reprises après repas/pause, export téléchargé puis vérifié, purge/restauration,
erreur IndexedDB puis Réessayer, erreur de réglage avec champ restauré, ajout
d’une pause dans un intervalle vide et rechargement hors ligne sous service worker.
L’écran **Données et sauvegardes** est vérifié depuis les réglages et le rappel,
avec Retour, rechargement, durée persistée et absence de débordement à 320 px.
Le bandeau de sauvegarde est aussi testé avec une police agrandie.
Les trois parcours d’erreur ajoutés couvrent :
- Échec d’écriture de la conservation : erreur visible, champ restauré,
base inchangée, valeur conservée après rechargement et nouvelle tentative réussie.
- Export JSON valide mais modifié : refus de vérification sans changement des
journées, postes, réglages ou statut de sauvegarde ; le fichier original peut
ensuite être vérifié.
- Échec de suppression des journées après écriture de la sauvegarde et suppression
des postes : annulation de toute la transaction, y compris restauration de la
sauvegarde précédente ; le dialogue reste ouvert et permet de réessayer.
Les captures MCP avaient été refusées par le contrôle d’autorisation. Les tests
Playwright ont été autorisés comme vérification automatisée isolée : aucune trace,
vidéo ou capture n’est produite, aucun contenu de page n’est transmis à MCP.
Commandes : `npx playwright install chromium` puis `npm run validate:all`.
Un navigateur installé peut être choisi avec `PARDIS_CHROME_PATH`.
La CI installe Chromium avec ses dépendances et lance les mêmes parcours.
`docker compose config --quiet` a été validé avec des variables d’exemple.
Cette sandbox ne pouvait pas lancer les conteneurs (socket Docker indisponible),
mais l'opérateur confirme que le déploiement Docker de production et son job
CI/CD fonctionnent et sont en service depuis plusieurs commits. La VM qui héberge
l'instance reçoit une sauvegarde quotidienne. Celle-ci protège l'hébergement et
le service de synchronisation, mais pas les données locales conservées dans les
navigateurs des utilisateurs.
## Vérifications sur appareils réels à terminer avant publication
- [ ] Sur petit téléphone (320 px), parcourir Récap → Voir les détails → Corriger,
retourner au récapitulatif et vérifier date, copie et statistiques.
- [ ] Au clavier, vérifier les pages Détails, Automatismes, Jours non travaillés
et Données et sauvegardes, le focus initial, Tab et Maj+Tab dans chaque dialogue,
Échap et restitution au bouton d’origine. Inclure remplacement et purge.
- [ ] Vérifier Retour du navigateur et Retour de la PWA depuis les pages filles
des réglages et du récapitulatif.
- [ ] Annuler la reprise depuis chacun des deux boutons : aucun changement.
Double appui et deux onglets réels : un seul poste de reprise.
- [ ] Installer la PWA puis démarrer hors ligne, suspendre et rouvrir.
Tester une mise à jour sans interrompre une journée active.
- [ ] Exporter sur iOS/Safari et Android/Chrome, vérifier le fichier téléchargé,
restaurer par fusion puis remplacer sur un profil de test.
- [ ] Simuler IndexedDB inaccessible : erreur visible et Réessayer.
- [ ] Vérifier les libellés d’ajustement de l’import sur un historique endommagé.
- [ ] Vérifier les dates affichées avant purge et récupérer sa sauvegarde.
- [ ] Depuis les réglages puis le rappel, ouvrir Données et sauvegardes, modifier
la durée, recharger et vérifier sa persistance sans suppression de journées.
### Consigner la recette manuelle
Utiliser des profils et des données de test. Pour Android/Chrome et iOS/Safari,
prendre une version stable actuelle et consigner les versions exactes. Aucun de
ces contrôles n’a été exécuté sur appareil réel dans cet environnement.
| Environnement | Appareil et versions OS/navigateur | Date | Résultat |
| --- | --- | --- | --- |
| Android / Chrome, PWA installée | À renseigner | — | À effectuer |
| iOS / Safari, ajout à l’écran d’accueil | À renseigner | — | À effectuer |
| Chromium sur ordinateur, navigation au clavier | À renseigner | — | Recette manuelle à effectuer |
Pour chaque environnement, relever les résultats d’export/vérification/import
(fusion et remplacement), démarrage hors ligne, suspension/réouverture, Retour
et affichage étroit. Pour les dialogues de remplacement et de purge, relever
aussi le focus initial, Tab/Maj+Tab, Échap et la restitution du focus. Une mise
à jour doit être proposée sans interrompre une journée active. Ne cocher les
vérifications qu’après leur exécution ; consigner les anomalies avec le parcours
et les versions utilisées.
## Suivi de fiabilité et limites connues
- La purge au lancement est désactivée pour protéger les imports anciens.
La purge manuelle conserve une sauvegarde dans IndexedDB, récupérable depuis
**Données et sauvegardes**. Modifier la durée de conservation ne supprime rien.
Cette copie n’est pas une sauvegarde externe et disparaît si les
données du site sont effacées. Une seule sauvegarde avant purge est conservée.
- La date du dernier export **préparé** n’est pas une preuve que le
téléchargement a été conservé : le navigateur ne confirme pas cette étape
automatiquement. Une date distincte est marquée vérifiée après relecture du fichier
téléchargé et comparaison de son empreinte complète. Le rappel se déclenche
après sept jours sans export vérifié. La restauration par import et la
récupération après purge sont testées, mais conserver une copie externe reste
nécessaire.
- Les journées dont tous les postes sont automatiques sont signalées comme
déduites et à vérifier. Leur statut métier reste « terminée » ; une étape formelle
de validation et son intégration aux statistiques restent à définir. Cette
fonctionnalité reste au backlog ; la finalisation actuelle conserve les
règles de calcul existantes.
- Tester les navigateurs et téléphones réellement ciblés avec une matrice de
versions, notamment changement d’heure, minuit et suspension prolongée.
- Le nom IndexedDB `TimeLogDB` et l’identifiant `timelog-export` restent stables
pour préserver les données. Les noms visibles et les icônes utilisent Pardis.
- Avant changement de domaine : exporter sur l’ancien domaine, vérifier le fichier,
ouvrir le nouveau domaine, importer et comparer journées, postes et réglages.
Les données locales et le service worker ne migrent pas entre origines.
- La configuration Caddy conserve les règles de revalidation HTML/service worker
et de cache des fichiers hachés ; vérifier les en-têtes sur le domaine publié.

418
README.fr.md Normal file
View File

@@ -0,0 +1,418 @@
# Pardis
PWA hors ligne de suivi des heures de chantier. Tant que l’application est ouverte,
elle peut démarrer automatiquement la journée et proposer sa clôture. Chaque tâche
commence là où la précédente s’est terminée. Les journées clôturées ont un
récapitulatif concis, prêt à être recopié dans un registre papier.
Conçue pour l’extérieur : contraste élevé, grandes zones tactiles et utilisable
avec des gants. Les données sont enregistrées localement dans IndexedDB. Une
synchronisation facultative chiffrée de bout en bout les partage entre appareils
autorisés, sans inscription par e-mail ni mot de passe.
Version anglaise : [README.md](README.md).
## Développement
```sh
npm install
npm run dev # http://localhost:5173
npm test # tests unitaires et de régression IndexedDB
npm run check # vérification Svelte
npm run build # build de production dans dist/
npm run validate # tests, vérification Svelte et build complet de la PWA
npx playwright install chromium # installer le navigateur pour les tests UI
npm run test:e2e # profils vierges, données synthétiques et test PWA hors ligne
npm run validate:all # inclure les tests navigateur dans la validation complète
```
## Exporter et importer les données
Dans **Réglages → Gérer les données et sauvegardes**, l’écran **Données et
sauvegardes** regroupe l’export, l’import, la vérification des sauvegardes et la
durée de conservation. Le rappel **Sauvegarder** ouvre aussi cet écran.
Les données restent disponibles dans le navigateur sur cet appareil. La synchronisation
chiffrée peut être activée séparément pour les partager avec vos autres appareils.
Effacer les données du site ou perdre l’appareil peut faire perdre l’historique :
gardez une copie vérifiée du fichier exporté sur un autre support.
**Exporter les données** télécharge un fichier
`pardis-AAAA-MM-JJ.json`. Il contient toutes les journées conservées, leurs postes
et les réglages enregistrés. L'export fonctionne hors ligne et ne modifie pas les
données de l'appareil. Les identifiants internes des postes sont recréés à l'import.
Pour restaurer une sauvegarde, choisissez **Importer un fichier…** puis vérifiez
le nom du fichier, les compteurs et les éventuels ajustements signalés. Deux modes
sont disponibles :
- **Fusionner** ajoute seulement les dates absentes. Une date déjà présente est
conservée intégralement, même si le fichier contient une autre version de ses
postes. Les réglages locaux sont conservés. Importer deux fois le même fichier
ne crée pas de doublons.
- **Remplacer tout** demande une confirmation, puis remplace toutes les journées,
tous les postes et les réglages, y compris une journée locale en cours. Si le
fichier ne contient pas de réglages, les valeurs par défaut sont rétablies.
Les journées importées sont enregistrées comme terminées. Pour une journée
exportée en cours, le dernier poste est arrêté à l'heure de l'export : le temps
écoulé est conservé. Les intervalles sans travail déjà présents sont conservés.
Une sauvegarde vide est valide et peut servir à restaurer un appareil vide.
Le format JSON est identifié par `format: "timelog-export"` et `version: 2`. Le format v1 reste importable. Les anciennes applications refusent le format v2 pour éviter de transformer les pauses en travail.
Les fichiers étrangers, versions non prises en charge, dates en double et réglages
invalides sont refusés. Les postes inutilisables et les corrections de plages
horaires sont signalés avant confirmation. La limite de taille est de 20 Mo.
L'écriture est transactionnelle : un échec annule le remplacement ou la fusion
dans son intégralité. Le récapitulatif, le thème et les suggestions sont ensuite
actualisés sans recharger la page.
Dans **Durée de conservation**, **Conserver (jours)** détermine les journées
anciennes proposées à la purge. Modifier cette durée ne supprime rien ; la purge
automatique est désactivée. L’historique conservé alimente les suggestions et les
statistiques. La dernière journée terminée et les journées actives sont conservées.
**Purger maintenant** présente les dates concernées et demande confirmation.
Une sauvegarde locale est conservée avant la suppression ; **Récupérer la
sauvegarde avant purge** permet de la fusionner ou de remplacer les données,
puis de l’exporter après restauration. Cette copie est remplacée à la prochaine
purge et disparaît si les données du site sont effacées : un fichier externe
reste nécessaire.
Les tests d'import/export couvrent le format, les réglages, la fidélité des durées,
les fichiers invalides, la fusion, le remplacement et l'annulation transactionnelle
sur une IndexedDB simulée. `src/lib/fixtures/backup.json` est le jeu d'essai fourni ;
il n'est pas publié avec l'application.
## Journées, reprise et historique
- Une journée oubliée est clôturée au retour dans l'application, à son heure de
fin prévue (prolongations comprises), sans jamais précéder la dernière activité
enregistrée. Cette heure est une estimation : l'application ne mesure pas le
travail lorsqu'elle est fermée. Le dernier poste conserve ainsi sa durée jusqu'à
cette limite, au lieu d'être ramené à zéro.
- Une journée terminée aujourd’hui propose **Reprendre la journée** avec confirmation : prolonger le dernier poste, ou créer une pause jusqu’à maintenant (choix initial). Un dernier repas ou une pause garde sa nature ; le travail reprend dans un nouveau poste. Les journées passées se corrigent par **Voir les détails → Corriger cette journée**.
- La création, la clôture et la reprise sont transactionnelles. Les actions
simultanées sur le même poste ne créent pas de doublons ; une journée clôturée
ne peut pas recevoir un nouveau poste actif. La purge épargne les journées
actives et conserve toujours la dernière journée terminée.
- Les horaires de déjeuner affichent un rappel puis une heure indicative de
reprise. Les transitions restent manuelles sauf si l'option **Pause déjeuner**
est activée dans les automatismes.
- Dans **Récap**, le sélecteur **Journée à consulter** permet d'afficher et de copier
chaque journée terminée encore conservée, y compris les journées importées.
Les tests couvrent notamment les clôtures oubliées, les reprises, les appels
concurrents, les retours d'horloge, les échecs transactionnels et la conservation.
## Automatismes
Dans **Réglages → Automatismes**, chaque comportement a son interrupteur :
| Option | Comportement |
| --- | --- |
| Ouverture | Démarrer aux horaires et jours prévus. Activée par défaut, comme auparavant. |
| Début de journée | Au bouton « Arrivé sur site », proposer la dernière tâche de travail de la précédente journée enregistrée, hors repas et transport. |
| Pause déjeuner | Ouvrir la pause à l'heure habituelle et reprendre la tâche précédente après la durée réglée. Reprendre le type de repas et le restaurant de la précédente journée enregistrée. |
| Arrivée sur chantier | Lors du repas automatique, transformer un transport initial non renseigné en un transport de 1/5/15/30 minutes, selon l'arrondi, suivi de la tâche précédente. |
| Fin de demi-journée | À la pause ou à la clôture, remplacer une plage automatique non renseignée par la dernière tâche de travail connue. Conserver tout poste manuel. |
| Fin de journée | Clôturer à l'heure prévue sans confirmation. |
Les cinq nouvelles options sont désactivées par défaut. Elles sont conservées
dans les sauvegardes et rétablies lors d'un import en remplacement. L'option
**Arrivée sur chantier** intervient seulement avec un repas automatique.
Sans cette estimation de transport, **Fin de demi-journée** peut remplacer le
transport initial non renseigné par du travail sur toute la plage. Une tâche de
travail déjà présente suffit : aucun doublon n'est ajouté à la demi-journée.
« Précédente » désigne la dernière journée terminée enregistrée avant la date
courante, y compris après un week-end. La complétion utilise la dernière tâche
de travail connue ; sans historique de travail, elle utilise **Travaux sur site**.
Les postes produits automatiquement portent la mention **Auto**. Les corriger
les rend manuels et les protège des déductions suivantes. Les saisies anciennes
sans cette information sont considérées comme manuelles.
Une pause commencée ou corrigée manuellement n'est pas terminée par l'automatisme
de déjeuner. Un choix manuel du type ou du restaurant est conservé. Une pause
supprimée n'est pas recréée. Désactiver l'option arrête les transitions suivantes ;
cela ne supprime pas les postes déjà enregistrés. Une durée de déjeuner nulle
n'ajoute aucune pause automatique. La durée d'une pause automatique déjà commencée
est conservée si le réglage change ensuite.
L'app vérifie l'horloge toutes les 30 secondes et au retour à l'écran. Une PWA
fermée ou suspendue ne peut pas exécuter du code à heure fixe : à la réouverture,
les étapes manquées sont rattrapées avec leurs horaires prévus, sans écraser une
action manuelle plus tardive. Les journées déjà clôturées restent intactes et les
dates entièrement absentes ne sont pas créées. Pour une journée sans intervention,
activer les options voulues et ouvrir l'app au moins une fois pour cette journée.
Les prolongations et la reprise d'une journée sont respectées. Un démarrage
manuel laisse au moins une heure avant une clôture automatique. Les transitions
automatiques sont transactionnelles : une erreur annule toute l'opération et
des appels simultanés ne créent pas de doublons.
## Jours non travaillés
Dans **Réglages → Jours non travaillés**, ajouter les dates à bloquer à l'avance.
La liste est triée par date ; chaque date peut être modifiée ou supprimée.
Les doublons sont refusés. Ces exceptions priment sur les jours de semaine
habituellement travaillés et empêchent l'ouverture automatique, y compris lors
du rattrapage à la réouverture de l'app ou hors ligne.
Aucune journée ni aucun poste n'est créé pour une date bloquée. Les journées
déjà commencées ou terminées restent conservées. Dans **Journée**, le bouton
**Démarrer quand même** permet un démarrage manuel exceptionnel.
Retirer une date rétablit les règles habituelles de démarrage automatique.
La liste est enregistrée avec les réglages et incluse dans l'export des données.
Un import en remplacement rétablit la liste de la sauvegarde ; un import en
fusion conserve la liste locale, comme les autres réglages. Une ancienne
sauvegarde sans cette liste la réinitialise à vide lors d'un remplacement.
## Corriger les postes et les journées terminées
Le **Total des heures de présence** exclut les pauses et le repas. Le repas reste affiché
séparément dans le récapitulatif et le texte copié. Les totaux suivent le réglage
d’arrondi existant.
Dans **Journée**, touchez un poste dans **Postes de la journée** pour modifier
son début, sa fin, sa tâche ou son client. Les boutons **Poste ordinaire**,
**Repas** et **Pause** permettent de choisir la nature du poste. Le type interne
d'un transport déjà enregistré est conservé quand on corrige ses horaires ou
son libellé.
Les sélecteurs d'heure ne proposent pas de date : ils conservent la date du
poste. Les minutes et les boutons d'ajustement suivent l'arrondi choisi dans
les réglages (1, 5, 15 ou 30 minutes). Les tâches, clients et restaurants utilisent
les mêmes suggestions et la même saisie libre que l'écran de la journée.
Les corrections sont enregistrées ensemble avec **Enregistrer**. Annuler ne
modifie rien ; ouvrir puis valider un horaire inchangé conserve sa valeur
enregistrée. Le début du premier
poste et la fin du dernier définissent les bornes de la journée. Modifier une
limite intermédiaire ajuste la limite du poste voisin, sans chevauchement.
La fin d'un poste encore en cours reste pilotée par les boutons de changement
de tâche ou de clôture.
**Ajouter un poste oublié** permet de choisir une plage existante puis les
horaires du poste à insérer dans cette plage. Le début et la fin sont préremplis
avec les deux bornes connues (jusqu'à l'heure actuelle pour un poste en cours).
Il suffit de changer une seule borne pour corriger le début ou la fin de la plage.
Les portions avant et après sont
conservées avec leur tâche et leur client. Par exemple, un poste 08:00–12:00
peut devenir 08:00–09:00, une tâche oubliée 09:00–10:00, puis 10:00–12:00.
Le bouton **Repas** permet aussi d’ajouter un déjeuner oublié et de renseigner
panier ou restaurant ; **Pause** ajoute une pause hors repas. Pour ajouter juste
après une frontière existante, choisissez le poste qui commence à cette frontière.
Dans **Récap**, choisissez la date puis **Voir les détails → Corriger cette
journée** : les mêmes actions sont disponibles sans réouverture, même si une autre journée est en
cours. Le récapitulatif et son texte à copier sont recalculés après chaque
enregistrement. La suppression d'un poste demande confirmation ; le voisin
absorbe sa durée, et le dernier poste d'une journée ne peut pas être supprimé.
Supprimer un repas réaffecte sa durée au poste voisin et peut donc changer le
total des heures de présence.
Chaque correction est transactionnelle. Les horaires inversés, les insertions
hors de la plage choisie et les formulaires devenus périmés après une autre
modification sont refusés sans écriture partielle.
## Statistiques
Ouvrez **Réglages → Consulter les statistiques**. Le bouton **Stats** de la barre
du bas n'apparaît que sur cette page ; les autres écrans gardent leurs trois
boutons habituels. La page fonctionne hors ligne. Les calculs de statistiques restent sur l’appareil ;
la synchronisation facultative peut partager leur historique source chiffré.
Les filtres couvrent tout l'historique, le mois, l'année, les 30 derniers jours
ou une plage de dates inclusive. Les journées en cours sont exclues par défaut.
L'option pour les inclure affiche des cumuls et détails provisoires. Les moyennes
de « Repères et observations », la médiane, les extrêmes et les profils par jour
de semaine restent calculés sur les seules journées terminées enregistrées.
Les statistiques présentent :
- Les heures de présence hors repas et pauses, les pauses, le transport identifié, les nombres de
journées, postes, tâches et clients.
- Les heures de présence par client et par tâche, leurs parts des heures de présence, les nombres de
journées concernées et les détails croisés, accessibles en ouvrant une ligne.
- L'évolution par mois ou semaine, sans inventer des heures pour les jours absents.
- Les journées avec repas panier ou restaurant, les restaurants fréquentés,
les durées de pause et les journées sans pause enregistrée. Plusieurs fragments
de repas le même jour ne créent pas plusieurs journées de repas.
- Les moyennes, la médiane, les journées extrêmes, les principales concentrations
de temps par client et tâche, et le détail quotidien.
- Les clients ou restaurants non renseignés, les postes à zéro après arrondi,
les journées vides, les plages non couvertes et les chevauchements éventuels.
Le calcul reprend l'arrondi du récapitulatif et exclut toujours le repas et les pauses des heures de présence.
Seules les données encore conservées sont analysées : les jours absents ou purgés
ne permettent pas de conclure à une absence de travail. Le transport identifié
repose sur le type enregistré, pas sur une déduction à partir du nom de la tâche.
En développement, la console expose `timelog.seed()` pour générer 20 jours
d’historique réaliste (utile pour tester le moteur de suggestions) et
`timelog.wipe()` pour l’effacer.
## Déploiement
La pile comprend l’application statique servie par Caddy et une API de synchronisation
facultative adossée à SQLite dans un volume persistant. Caddy obtient et renouvelle
le certificat HTTPS. La synchronisation reste désactivée dans chaque client jusqu’à
son activation explicite. Voir [SYNC.md](SYNC.md) pour l’association, le chiffrement,
la récupération, les quotas et les sauvegardes du serveur.
```sh
cp .env.example .env
$EDITOR .env # renseigner SITE_ADDRESS, ACME_EMAIL et TRUST_PROXY
docker compose up -d --build
```
Prérequis :
- `SITE_ADDRESS` doit déjà pointer vers le serveur.
- `ACME_EMAIL` est obligatoire et doit être une adresse de contact valide.
- Les ports **80 et 443** doivent être accessibles depuis Internet. Le port 80 est
obligatoire : le défi ACME l’utilise pour vérifier la propriété du domaine.
Les certificats sont stockés dans le répertoire monté `./caddy/data`. Conservez-le :
sans lui, Caddy redemande un certificat à chaque recréation et atteindra la limite
de requêtes de Let's Encrypt après quelques redémarrages.
Pour mettre à jour, récupérez les changements et reconstruisez l’image :
```sh
git pull && docker compose up -d --build
```
Les clients déjà ouverts ne se rechargent pas seuls. Chaque application vérifie
toutes les heures si une nouvelle version est disponible et affiche une bannière
**« Installer »**, uniquement lorsqu’aucune journée n’est en cours, afin qu’une
mise à jour n’interrompe jamais une journée de travail.
### HTTPS est obligatoire
Les navigateurs n’enregistrent un service worker que dans un contexte sécurisé. En
`http://` simple, sur une adresse IP ou un nom d’hôte, l’application se charge mais
n’a **pas de fonctionnement hors ligne, ne peut pas être installée et ne se met jamais
à jour** - ce qui annule l’intérêt de sa conception. `localhost` est la seule
exception, d’où le fonctionnement de `npm run dev` sans certificat.
### Derrière un reverse proxy existant
La configuration Caddy actuelle termine elle-même TLS et accepte le protocole PROXY
depuis l’adresse ou le CIDR défini dans `TRUST_PROXY`. Indiquez l’adresse réelle du
proxy amont ; `192.0.2.10/32` dans `.env.example` est une valeur fictive de documentation.
`LISTEN_IP` sélectionne l’interface locale des ports publiés et vaut `0.0.0.0` par
défaut. Un déploiement qui termine TLS ailleurs doit disposer de sa propre configuration
Caddy et de ports ; modifier `SITE_ADDRESS` seul ne configure pas ce mode.
L’application doit être servie à la racine de son domaine. `start_url` et la portée
du service worker valent tous deux `/` : un hébergement dans un sous-répertoire ne
fonctionnera pas sans modifier le manifeste et `base` dans Vite.
## Architecture
Deux décisions structurent le reste de l’application.
**Les horodatages bruts sont enregistrés ; l’arrondi n’est appliqué qu’à l’affichage.**
Modifier l’arrondi corrige donc aussi les journées passées. Deux postes adjacents
partagent le même instant enregistré - la fin du poste *N* est le début du poste
*N+1* - afin que l’arrondi ne puisse jamais créer de trou entre eux. Les sélecteurs
d’heure suivent la grille d’arrondi choisie ; ouvrir puis valider un horaire inchangé
préserve son horodatage. Les récapitulatifs appliquent le même arrondi à l’affichage.
**Les suggestions sont déduites de l’historique, pas de tables de compteurs.** Rien
n’est à synchroniser : le modèle est reconstruit en mémoire à partir des postes.
En contrepartie, une durée de conservation courte dégrade les prédictions ; c’est
pourquoi elle est de deux ans par défaut.
```
src/lib/
time.js dates locales, arrondi, formatage (pur)
predict.js moteur de suggestions (pur)
resume.js génération du texte du registre (pur)
db.js schéma Dexie et invariant de chaîne
day.svelte.js machine à états du cycle de journée
```
Les fonctions pures et les opérations de base de données et de cycle de vie ont des
tests de régression. `db.js` regroupe les modifications des journées et des postes
dans des transactions communes, notamment la création, la clôture, la reprise et la
correction des bornes.
## Pauses et nouveaux parcours
**Journée → Commencer une pause** ferme le poste courant. **Reprendre le travail**
propose la dernière tâche de travail et son client, immédiatement modifiables.
Une pause a ses propres horaires et son libellé, sans client ni type de repas.
Le détail montre tous les postes ; la consultation reste séparée de la correction.
Le récapitulatif et sa copie excluent les pauses. Le transport entre dans le total
des heures de présence, les repas et pauses en sont exclus. Seules les statistiques montrent le
cumul des pauses, séparément des repas. Les intervalles vides importés sont préservés.
**Réglages → Gérer les automatismes** et **Gérer les jours non travaillés** ouvrent
des pages dédiées ; le bouton Retour du navigateur fonctionne via les routes hash.
**Gérer les données et sauvegardes** ouvre aussi une page dédiée, avec retour aux
réglages, conservation de la durée après rechargement et accès hors ligne.
Les réglages sont écrits par transaction sur la dernière valeur persistée pour
préserver les modifications d’autres onglets. Les erreurs de démarrage et
actualisation sont affichées avec une action Réessayer.
La checklist avant production et les validations restant à exécuter sont suivies
dans [PRODUCTION.md](PRODUCTION.md).
## Sauvegarde vérifiée et validation navigateur
**Exporter les données** prépare le fichier et enregistre son empreinte complète.
**Vérifier le fichier exporté…** relit le fichier téléchargé, contrôle qu’il est
importable et compare son contenu à l’export. La date de la dernière sauvegarde
vérifiée est distincte de celle du dernier export préparé. Après sept jours sans
export vérifié, Pardis affiche un rappel pendant son utilisation. L’export reste local ; conservez le fichier hors de cet appareil. La synchronisation
facultative utilise un coffre distinct et ne remplace pas cette sauvegarde.
La purge affiche une liste figée de journées. Sauvegarde et suppression partagent
une transaction ; une correction concurrente bloque la purge et demande une
nouvelle consultation. Les autres journées récemment importées restent intactes.
Les tests Playwright utilisent des contextes neufs et des données synthétiques,
à 320 px. Ils vérifient consultation/correction, focus, Retour, reprise à deux
onglets, export vérifié, purge/restauration et rechargement hors ligne. Ils couvrent
aussi le nouvel écran Données, le retour du champ de conservation à sa valeur
persistée après un échec, le refus d’un export modifié et l’annulation complète
d’une purge en erreur, avec préservation de la sauvegarde précédente. Traces,
vidéos et captures sont désactivées. Pour un Chrome déjà installé, définir
`PARDIS_CHROME_PATH` avec son chemin ; sinon installer Chromium avec Playwright.
La CI installe Chromium Headless Shell et ses dépendances avant de lancer ces
tests. Elle télécharge uniquement la variante utilisée par les tests sans interface
et accorde 180 secondes à chaque connexion de téléchargement, avec les nouvelles
tentatives intégrées à Playwright. L’installation dispose de 20 minutes et le job
de validation de 30 minutes. Une erreur d’installation bloque le déploiement.
## Synchronisation facultative entre appareils
**Données et sauvegardes → Synchronisation et appareils** permet de créer un coffre
chiffré sans e-mail ni mot de passe, puis de lier des appareils avec un code temporaire
ou un QR code et une confirmation sur l’écran dédié. La saisie reste locale et
fonctionne hors ligne. Les journées concurrentes sont conservées pour résolution.
Un document de secours permet de retrouver l’accès après perte des appareils.
Les règles métier sont partagées ; le thème et le suivi des sauvegardes restent
locaux. Un appareil est responsable des automatismes, avec proposition de transfert
à l’ouverture sur un appareil secondaire. Les demandes d’association ne produisent
aucune alerte ailleurs dans l’application.
Le protocole, les limites de sécurité, le fonctionnement des suppressions et les
commandes de sauvegarde/restauration du serveur sont documentés dans [SYNC.md](SYNC.md).
Pour le développement, lancer aussi `npm run sync:server` avec Node.js 24.
## Licence et crédits typographiques
Le code source de Pardis est distribué sous la licence GNU General Public License,
version 3 uniquement (GPL-3.0-only). Le texte intégral se trouve dans
[LICENSE.md](LICENSE.md). Les fichiers de polices fournis sont des œuvres distinctes,
distribuées sous la SIL Open Font License, version 1.1. Leurs mentions de droits
d’auteur et le texte de cette licence figurent dans [public/fonts/OFL.txt](public/fonts/OFL.txt).
Copyright (C) 2026 EI Adrien Weiskopff.
- **Bebas Neue**, par Ryoichi Tsunekawa - SIL Open Font License 1.1.
- **Charis SIL** - SIL Open Font License 1.1. Voir le [site officiel de Charis SIL](https://software.sil.org/fr/charis/).

613
README.md
View File

@@ -1,395 +1,394 @@
# Pardis # Pardis
Offline-first PWA for tracking hours on site. While the app is running, it can start An offline-first progressive web app (PWA) for tracking on-site working hours. While
the day automatically and prompt for its end. Each task begins where the previous the app is open, it can start a workday automatically and prompt to close it. Each
one ended. Closed days have concise summaries ready to copy into a paper logbook. task starts when the previous one ends. Closed days have concise summaries ready to
copy into a paper logbook.
Built for use outdoors: high contrast, large targets, usable with gloves on. Data Designed for outdoor use: high contrast, large touch targets, and usable with gloves.
is saved locally in IndexedDB. Optional end-to-end encrypted synchronization shares Data is stored locally in IndexedDB. Optional end-to-end encrypted synchronization
it between authorized devices without e-mail or password registration. shares it between authorized devices without requiring an email address or password.
French documentation: [README.fr.md](README.fr.md).
## Development ## Development
```sh npm install
npm install npm run dev # http://localhost:5173
npm run dev # http://localhost:5173 npm test # unit and IndexedDB regression tests
npm test # unit and IndexedDB regression tests npm run check # Svelte diagnostics
npm run check # svelte-check npm run build # production build into dist/
npm run build # production build into dist/ npm run validate # tests, Svelte diagnostics and full PWA build
npm run validate # tests, Svelte check and full PWA build npx playwright install chromium # install the browser for UI tests
npx playwright install chromium # install the browser for UI tests npm run test:e2e # fresh profiles, synthetic fixtures and offline PWA test
npm run test:e2e # fresh profiles, synthetic fixtures and offline PWA test npm run validate:all # include browser tests in full validation
npm run validate:all # include browser tests in full validation
```
## Exporter et importer les données ## Exporting and importing data
Dans **Réglages → Gérer les données et sauvegardes**, l’écran **Données et In **Settings (Réglages) → Manage data and backups (Gérer les données et
sauvegardes** regroupe l’export, l’import, la vérification des sauvegardes et la sauvegardes)**, the **Data and backups (Données et sauvegardes)** screen groups
durée de conservation. Le rappel **Sauvegarder** ouvre aussi cet écran. export, import, backup verification, and retention settings. The **Back up
(Sauvegarder)** reminder also opens this screen.
Les données restent disponibles dans le navigateur sur cet appareil. La synchronisation Data remains available in the browser on this device. Optional encrypted
chiffrée peut être activée séparément pour les partager avec vos autres appareils. synchronization can share it with your other devices. Clearing site data or losing
Effacer les données du site ou perdre l’appareil peut faire perdre l’historique : the device can result in lost history: keep a verified copy of the exported file on
gardez une copie vérifiée du fichier exporté sur un autre support. another storage device.
**Exporter les données** télécharge un fichier **Export data (Exporter les données)** downloads a file named
`pardis-AAAA-MM-JJ.json`. Il contient toutes les journées conservées, leurs postes pardis-YYYY-MM-DD.json. It contains all retained days, their entries, and saved
et les réglages enregistrés. L'export fonctionne hors ligne et ne modifie pas les settings. Export works offline and does not modify the device's data. Internal entry
données de l'appareil. Les identifiants internes des postes sont recréés à l'import. identifiers are regenerated on import.
Pour restaurer une sauvegarde, choisissez **Importer un fichier…** puis vérifiez To restore a backup, choose **Import file… (Importer un fichier…)**, then review the
le nom du fichier, les compteurs et les éventuels ajustements signalés. Deux modes file name, counts, and any reported adjustments. Two modes are available:
sont disponibles :
- **Fusionner** ajoute seulement les dates absentes. Une date déjà présente est - **Merge (Fusionner)** adds only dates that are not already present. An existing
conservée intégralement, même si le fichier contient une autre version de ses date is kept in full even if the file contains a different version of its entries.
postes. Les réglages locaux sont conservés. Importer deux fois le même fichier Local settings are preserved. Importing the same file twice creates no duplicates.
ne crée pas de doublons. - **Replace all (Remplacer tout)** asks for confirmation, then replaces every day,
- **Remplacer tout** demande une confirmation, puis remplace toutes les journées, entry, and setting, including a local day currently in progress. If the file has no
tous les postes et les réglages, y compris une journée locale en cours. Si le settings, defaults are restored.
fichier ne contient pas de réglages, les valeurs par défaut sont rétablies.
Les journées importées sont enregistrées comme terminées. Pour une journée Imported days are marked as closed. For a day exported while in progress, the last
exportée en cours, le dernier poste est arrêté à l'heure de l'export : le temps entry is stopped at the export time, preserving elapsed time. Existing gaps between
écoulé est conservé. Les intervalles sans travail déjà présents sont conservés. work periods are preserved. An empty backup is valid and can restore an empty device.
Une sauvegarde vide est valide et peut servir à restaurer un appareil vide.
Le format JSON est identifié par `format: "timelog-export"` et `version: 2`. Le format v1 reste importable. Les anciennes applications refusent le format v2 pour éviter de transformer les pauses en travail. The JSON format is identified by format: "timelog-export" and version: 2. Version 1
Les fichiers étrangers, versions non prises en charge, dates en double et réglages files remain importable. Older app versions reject version 2 to avoid turning breaks
invalides sont refusés. Les postes inutilisables et les corrections de plages into work. Foreign files, unsupported versions, duplicate dates, and invalid
horaires sont signalés avant confirmation. La limite de taille est de 20 Mo. settings are rejected. Unusable entries and time-range corrections are reported
L'écriture est transactionnelle : un échec annule le remplacement ou la fusion before confirmation. The size limit is 20 MB. Writes are transactional: a failure
dans son intégralité. Le récapitulatif, le thème et les suggestions sont ensuite cancels the entire merge or replacement. The summary, theme, and suggestions then
actualisés sans recharger la page. refresh without reloading the page.
Dans **Durée de conservation**, **Conserver (jours)** détermine les journées Under **Retention (Durée de conservation)**, **Keep (days) (Conserver (jours))**
anciennes proposées à la purge. Modifier cette durée ne supprime rien ; la purge determines which older days are offered for deletion. Changing this setting does not
automatique est désactivée. L’historique conservé alimente les suggestions et les delete anything; automatic deletion is disabled. Retained history feeds suggestions
statistiques. La dernière journée terminée et les journées actives sont conservées. and statistics. The latest closed day and active days are always kept.
**Purger maintenant** présente les dates concernées et demande confirmation. **Delete now (Purger maintenant)** lists affected dates and asks for confirmation.
Une sauvegarde locale est conservée avant la suppression ; **Récupérer la A local backup is kept before deletion. **Recover pre-deletion backup (Récupérer la
sauvegarde avant purge** permet de la fusionner ou de remplacer les données, sauvegarde avant purge)** can merge or replace data, then export it after recovery.
puis de l’exporter après restauration. Cette copie est remplacée à la prochaine This copy is replaced by the next deletion and is removed if site data is cleared; an
purge et disparaît si les données du site sont effacées : un fichier externe external file is still necessary.
reste nécessaire.
Les tests d'import/export couvrent le format, les réglages, la fidélité des durées, Import/export tests cover the format, settings, duration fidelity, invalid files,
les fichiers invalides, la fusion, le remplacement et l'annulation transactionnelle merge, replacement, and transaction rollback using a simulated IndexedDB.
sur une IndexedDB simulée. `src/lib/fixtures/backup.json` est le jeu d'essai fourni ; src/lib/fixtures/backup.json is the supplied test fixture; it is not published with
il n'est pas publié avec l'application. the app.
## Journées, reprise et historique ## Workdays, resuming, and history
- Une journée oubliée est clôturée au retour dans l'application, à son heure de - If a workday was left open, it is closed when the app is next opened, at its planned
fin prévue (prolongations comprises), sans jamais précéder la dernière activité end time (including overtime), but never before the last recorded activity. This is
enregistrée. Cette heure est une estimation : l'application ne mesure pas le an estimate: the app does not measure work while it is closed. The final entry
travail lorsqu'elle est fermée. Le dernier poste conserve ainsi sa durée jusqu'à keeps its duration up to that limit instead of being reset to zero.
cette limite, au lieu d'être ramené à zéro. - A day closed today offers **Resume workday (Reprendre la journée)** with
- Une journée terminée aujourd’hui propose **Reprendre la journée** avec confirmation : prolonger le dernier poste, ou créer une pause jusqu’à maintenant (choix initial). Un dernier repas ou une pause garde sa nature ; le travail reprend dans un nouveau poste. Les journées passées se corrigent par **Voir les détails → Corriger cette journée**. confirmation: continue the last entry, or create a break up to now (the default).
- La création, la clôture et la reprise sont transactionnelles. Les actions A final meal or break keeps its type; work resumes in a new entry. Past days can be
simultanées sur le même poste ne créent pas de doublons ; une journée clôturée corrected via **View details → Edit this day (Voir les détails → Corriger cette
ne peut pas recevoir un nouveau poste actif. La purge épargne les journées journée)**.
actives et conserve toujours la dernière journée terminée. - Creating, closing, and resuming a day are transactional. Simultaneous actions on
- Les horaires de déjeuner affichent un rappel puis une heure indicative de the same entry cannot create duplicates; a closed day cannot receive a new active
reprise. Les transitions restent manuelles sauf si l'option **Pause déjeuner** entry. Deletion skips active days and always keeps the latest closed day.
est activée dans les automatismes. - Lunch schedules show a reminder and an indicative return time. Transitions remain
- Dans **Récap**, le sélecteur **Journée à consulter** permet d'afficher et de copier manual unless **Lunch break (Pause déjeuner)** is enabled under automations.
chaque journée terminée encore conservée, y compris les journées importées. - In **Summary (Récap)**, the **Day to view (Journée à consulter)** selector shows
and copies every retained closed day, including imported days.
Les tests couvrent notamment les clôtures oubliées, les reprises, les appels Tests cover missed closures, resuming, concurrent calls, clock changes, transaction
concurrents, les retours d'horloge, les échecs transactionnels et la conservation. failures, and data retention.
## Automatismes ## Automations
Dans **Réglages → Automatismes**, chaque comportement a son interrupteur : In **Settings (Réglages) → Automations (Automatismes)**, each behavior has its own
switch:
| Option | Comportement | | Option | Behavior |
| --- | --- | | --- | --- |
| Ouverture | Démarrer aux horaires et jours prévus. Activée par défaut, comme auparavant. | | Opening | Start at the scheduled times and on scheduled days. Enabled by default, as before. |
| Début de journée | Au bouton « Arrivé sur site », proposer la dernière tâche de travail de la précédente journée enregistrée, hors repas et transport. | | Start of day | When tapping “Arrived on site” (Arrivé sur site), suggest the last work task from the previous recorded day, excluding meals and travel. |
| Pause déjeuner | Ouvrir la pause à l'heure habituelle et reprendre la tâche précédente après la durée réglée. Reprendre le type de repas et le restaurant de la précédente journée enregistrée. | | Lunch break | Start a break at the usual time and resume the previous task after the configured duration. Reuse the meal type and restaurant from the previous recorded day. |
| Arrivée sur chantier | Lors du repas automatique, transformer un transport initial non renseigné en un transport de 1/5/15/30 minutes, selon l'arrondi, suivi de la tâche précédente. | | Site arrival | During an automatic meal, turn an unspecified initial travel period into a travel entry of 1/5/15/30 minutes, according to the rounding setting, followed by the previous task. |
| Fin de demi-journée | À la pause ou à la clôture, remplacer une plage automatique non renseignée par la dernière tâche de travail connue. Conserver tout poste manuel. | | Half-day end | At a break or day closure, replace an unspecified automatic period with the last known work task. Preserve any manual entry. |
| Fin de journée | Clôturer à l'heure prévue sans confirmation. | | End of day | Close the day at the scheduled time without confirmation. |
Les cinq nouvelles options sont désactivées par défaut. Elles sont conservées The five newer options are disabled by default. They are included in backups and are
dans les sauvegardes et rétablies lors d'un import en remplacement. L'option restored by replacement imports. **Site arrival** only applies with an automatic
**Arrivée sur chantier** intervient seulement avec un repas automatique. meal. Without this travel estimate, **Half-day end** may replace unspecified initial
Sans cette estimation de transport, **Fin de demi-journée** peut remplacer le travel with work for the entire period. If a work task is already present, no
transport initial non renseigné par du travail sur toute la plage. Une tâche de duplicate is added to the half-day.
travail déjà présente suffit : aucun doublon n'est ajouté à la demi-journée.
« Précédente » désigne la dernière journée terminée enregistrée avant la date “Previous” means the latest recorded closed day before the current date, including
courante, y compris après un week-end. La complétion utilise la dernière tâche after a weekend. Completion reuses the most recent known work task; when there is no
de travail connue ; sans historique de travail, elle utilise **Travaux sur site**. work history, it uses “On-site work” (Travaux sur site). Automatically created
Les postes produits automatiquement portent la mention **Auto**. Les corriger entries are marked **Auto**. Editing one makes it manual and protects it from later
les rend manuels et les protège des déductions suivantes. Les saisies anciennes inference. Older entries without this marker are treated as manual.
sans cette information sont considérées comme manuelles.
Une pause commencée ou corrigée manuellement n'est pas terminée par l'automatisme A manually started or edited break is not ended by the lunch automation. A manually
de déjeuner. Un choix manuel du type ou du restaurant est conservé. Une pause chosen meal type or restaurant is preserved. A deleted break is not recreated.
supprimée n'est pas recréée. Désactiver l'option arrête les transitions suivantes ; Disabling an option stops future transitions; it does not remove entries already
cela ne supprime pas les postes déjà enregistrés. Une durée de déjeuner nulle saved. A zero lunch duration does not add an automatic break. The duration of an
n'ajoute aucune pause automatique. La durée d'une pause automatique déjà commencée automatic break already in progress is preserved if the setting changes later.
est conservée si le réglage change ensuite.
L'app vérifie l'horloge toutes les 30 secondes et au retour à l'écran. Une PWA The app checks the clock every 30 seconds and when it returns to the foreground.
fermée ou suspendue ne peut pas exécuter du code à heure fixe : à la réouverture, A closed or suspended PWA cannot run code at a scheduled time: on reopening, missed
les étapes manquées sont rattrapées avec leurs horaires prévus, sans écraser une steps are caught up at their planned times without overwriting a later manual action.
action manuelle plus tardive. Les journées déjà clôturées restent intactes et les Already closed days remain unchanged, and entirely missing dates are not created.
dates entièrement absentes ne sont pas créées. Pour une journée sans intervention, For a day with no intervention, enable the desired options and open the app at least
activer les options voulues et ouvrir l'app au moins une fois pour cette journée. once that day.
Les prolongations et la reprise d'une journée sont respectées. Un démarrage Overtime and resumed days are respected. A manual start leaves at least one hour
manuel laisse au moins une heure avant une clôture automatique. Les transitions before an automatic closure. Automatic transitions are transactional: an error
automatiques sont transactionnelles : une erreur annule toute l'opération et cancels the whole operation, and simultaneous calls cannot create duplicates.
des appels simultanés ne créent pas de doublons.
## Jours non travaillés ## Non-working days
Dans **Réglages → Jours non travaillés**, ajouter les dates à bloquer à l'avance. In **Settings (Réglages) → Non-working days (Jours non travaillés)**, add dates to
La liste est triée par date ; chaque date peut être modifiée ou supprimée. block in advance. The list is sorted by date; each date can be edited or removed.
Les doublons sont refusés. Ces exceptions priment sur les jours de semaine Duplicates are rejected. These exceptions take precedence over the usual weekdays
habituellement travaillés et empêchent l'ouverture automatique, y compris lors and prevent automatic opening, including catch-up when the app is reopened or used
du rattrapage à la réouverture de l'app ou hors ligne. offline.
Aucune journée ni aucun poste n'est créé pour une date bloquée. Les journées No day or entry is created for a blocked date. Days already started or closed are
déjà commencées ou terminées restent conservées. Dans **Journée**, le bouton preserved. **Start anyway (Démarrer quand même)** in **Workday (Journée)** allows an
**Démarrer quand même** permet un démarrage manuel exceptionnel. exceptional manual start. Removing a date restores the usual automatic start rules.
Retirer une date rétablit les règles habituelles de démarrage automatique.
La liste est enregistrée avec les réglages et incluse dans l'export des données. The list is saved with settings and included in data exports. A replacement import
Un import en remplacement rétablit la liste de la sauvegarde ; un import en restores the backup's list; a merge keeps the local list, like other settings. An
fusion conserve la liste locale, comme les autres réglages. Une ancienne older backup without this list resets it to empty on replacement.
sauvegarde sans cette liste la réinitialise à vide lors d'un remplacement.
## Corriger les postes et les journées terminées ## Editing entries and closed days
Le **Total des heures de présence** exclut les pauses et le repas. Le repas reste affiché The **Total hours present (Total des heures de présence)** excludes breaks and meals.
séparément dans le récapitulatif et le texte copié. Les totaux suivent le réglage Meals remain separate in the summary and copied text. Totals use the selected
d’arrondi existant. rounding setting.
Dans **Journée**, touchez un poste dans **Postes de la journée** pour modifier In **Workday (Journée)**, tap an entry under **Day entries (Postes de la journée)**
son début, sa fin, sa tâche ou son client. Les boutons **Poste ordinaire**, to edit its start, end, task, or client. **Regular entry (Poste ordinaire)**, **Meal
**Repas** et **Pause** permettent de choisir la nature du poste. Le type interne (Repas)**, and **Break (Pause)** buttons select the entry type. When editing the
d'un transport déjà enregistré est conservé quand on corrige ses horaires ou times or label of an existing travel entry, its internal type is preserved.
son libellé.
Les sélecteurs d'heure ne proposent pas de date : ils conservent la date du
poste. Les minutes et les boutons d'ajustement suivent l'arrondi choisi dans
les réglages (1, 5, 15 ou 30 minutes). Les tâches, clients et restaurants utilisent
les mêmes suggestions et la même saisie libre que l'écran de la journée.
Les corrections sont enregistrées ensemble avec **Enregistrer**. Annuler ne
modifie rien ; ouvrir puis valider un horaire inchangé conserve sa valeur
enregistrée. Le début du premier
poste et la fin du dernier définissent les bornes de la journée. Modifier une
limite intermédiaire ajuste la limite du poste voisin, sans chevauchement.
La fin d'un poste encore en cours reste pilotée par les boutons de changement
de tâche ou de clôture.
**Ajouter un poste oublié** permet de choisir une plage existante puis les Time selectors do not include a date: they keep the entry's date. Minutes and
horaires du poste à insérer dans cette plage. Le début et la fin sont préremplis adjustment buttons follow the selected rounding interval (1, 5, 15, or 30 minutes).
avec les deux bornes connues (jusqu'à l'heure actuelle pour un poste en cours). Tasks, clients, and restaurants use the same suggestions and free-text entry as the
Il suffit de changer une seule borne pour corriger le début ou la fin de la plage. workday screen.
Les portions avant et après sont
conservées avec leur tâche et leur client. Par exemple, un poste 08:00–12:00
peut devenir 08:00–09:00, une tâche oubliée 09:00–10:00, puis 10:00–12:00.
Le bouton **Repas** permet aussi d’ajouter un déjeuner oublié et de renseigner
panier ou restaurant ; **Pause** ajoute une pause hors repas. Pour ajouter juste
après une frontière existante, choisissez le poste qui commence à cette frontière.
Dans **Récap**, choisissez la date puis **Voir les détails → Corriger cette Corrections are saved together with **Save (Enregistrer)**. Cancel makes no changes;
journée** : les mêmes actions sont disponibles sans réouverture, même si une autre journée est en opening and saving an unchanged time preserves its stored value. The first entry's
cours. Le récapitulatif et son texte à copier sont recalculés après chaque start and last entry's end define the day boundaries. Editing an intermediate
enregistrement. La suppression d'un poste demande confirmation ; le voisin boundary adjusts the adjacent entry's boundary without overlap. The end of an active
absorbe sa durée, et le dernier poste d'une journée ne peut pas être supprimé. entry remains controlled by task changes or day closure.
Supprimer un repas réaffecte sa durée au poste voisin et peut donc changer le
total des heures de présence.
Chaque correction est transactionnelle. Les horaires inversés, les insertions **Add a missed entry (Ajouter un poste oublié)** lets you choose an existing period,
hors de la plage choisie et les formulaires devenus périmés après une autre then the times for an entry to insert into it. The start and end are prefilled with
modification sont refusés sans écriture partielle. the known boundaries (up to now for an active entry). Change either boundary to
adjust one side of the period. The portions before and after are preserved with
their original task and client. For example, 08:00–12:00 can become 08:00–09:00,
a missed task from 09:00–10:00, then 10:00–12:00. **Meal (Repas)** can add a missed
meal and set its type to packed or restaurant; **Break (Pause)** adds a non-meal
break. To add an entry immediately after an existing boundary, choose the entry
that starts at that boundary.
## Statistiques In **Summary (Récap)**, choose a date, then **View details → Edit this day (Voir les
détails → Corriger cette journée)**. The same actions are available without resuming
the day, even while another day is in progress. The summary and copied text are
recalculated after each save. Deleting an entry requires confirmation; its neighbor
absorbs its duration, and the final entry of a day cannot be deleted. Deleting a meal
assigns its duration to the neighboring entry and can therefore change total hours
present.
Ouvrez **Réglages → Consulter les statistiques**. Le bouton **Stats** de la barre Every correction is transactional. Reversed times, insertions outside the selected
du bas n'apparaît que sur cette page ; les autres écrans gardent leurs trois period, and forms made stale by another edit are rejected without a partial write.
boutons habituels. La page fonctionne hors ligne. Les calculs de statistiques restent sur l’appareil ;
la synchronisation facultative peut partager leur historique source chiffré.
Les filtres couvrent tout l'historique, le mois, l'année, les 30 derniers jours ## Statistics
ou une plage de dates inclusive. Les journées en cours sont exclues par défaut.
L'option pour les inclure affiche des cumuls et détails provisoires. Les moyennes
de « Repères et observations », la médiane, les extrêmes et les profils par jour
de semaine restent calculés sur les seules journées terminées enregistrées.
Les statistiques présentent : Open **Settings (Réglages) → View statistics (Consulter les statistiques)**. The
**Stats** button appears in the bottom bar only on this page; other screens keep
their usual three buttons. The page works offline. Statistics are calculated on the
device; optional synchronization may share their source history in encrypted form.
- Les heures de présence hors repas et pauses, les pauses, le transport identifié, les nombres de Filters cover all history, the month, the year, the last 30 days, or an inclusive
journées, postes, tâches et clients. date range. Days in progress are excluded by default. Enabling them shows
- Les heures de présence par client et par tâche, leurs parts des heures de présence, les nombres de provisional totals and details. Averages under **Highlights and observations
journées concernées et les détails croisés, accessibles en ouvrant une ligne. (Repères et observations)**, the median, extremes, and weekday profiles use only
- L'évolution par mois ou semaine, sans inventer des heures pour les jours absents. recorded closed days.
- Les journées avec repas panier ou restaurant, les restaurants fréquentés,
les durées de pause et les journées sans pause enregistrée. Plusieurs fragments
de repas le même jour ne créent pas plusieurs journées de repas.
- Les moyennes, la médiane, les journées extrêmes, les principales concentrations
de temps par client et tâche, et le détail quotidien.
- Les clients ou restaurants non renseignés, les postes à zéro après arrondi,
les journées vides, les plages non couvertes et les chevauchements éventuels.
Le calcul reprend l'arrondi du récapitulatif et exclut toujours le repas et les pauses des heures de présence. Statistics include:
Seules les données encore conservées sont analysées : les jours absents ou purgés
ne permettent pas de conclure à une absence de travail. Le transport identifié
repose sur le type enregistré, pas sur une déduction à partir du nom de la tâche.
In dev the console exposes `timelog.seed()` to generate 20 days of realistic history - Hours present excluding meals and breaks, breaks, identified travel, and counts of
(useful for exercising the suggestion engine) and `timelog.wipe()` to clear it. days, entries, tasks, and clients.
- Hours present by client and task, their shares of hours present, the number of
affected days, and cross-details available by opening a row.
- Monthly or weekly trends without inventing hours for missing days.
- Days with packed or restaurant meals, restaurants visited, break durations, and
days with no recorded break. Multiple meal fragments on one day count as one
meal day.
- Averages, median, extreme days, top client and task time concentrations, and
daily details.
- Unspecified clients or restaurants, entries rounded to zero, empty days, uncovered
periods, and possible overlaps.
Calculations use the summary's rounding and always exclude meals and breaks from
hours present. Only retained data is analyzed: missing or deleted days cannot be
taken as proof that no work happened. Identified travel uses the recorded entry type,
not an inference from the task name.
In development, the console exposes `timelog.seed()` to generate 20 days of realistic
history (useful for exercising suggestions) and `timelog.wipe()` to clear it.
## Deployment ## Deployment
The stack contains the static app served by Caddy and an optional-use synchronization The stack consists of the static app served by Caddy and an optional synchronization
API backed by SQLite in a persistent volume. Caddy obtains and renews the HTTPS API backed by SQLite in a persistent volume. Caddy obtains and renews the HTTPS
certificate. Synchronization stays disabled in each client until explicitly activated. certificate. Synchronization remains disabled in each client until explicitly
See [SYNC.md](SYNC.md) for pairing, encryption, recovery, quotas and server backups. activated. See [SYNC.md](SYNC.md) for pairing, encryption, recovery, quotas, and
server backups.
```sh cp .env.example .env
cp .env.example .env $EDITOR .env # set SITE_ADDRESS, ACME_EMAIL and TRUST_PROXY
$EDITOR .env # set SITE_ADDRESS, ACME_EMAIL and TRUST_PROXY docker compose up -d --build
docker compose up -d --build
```
Requirements: Requirements:
- `SITE_ADDRESS` must already resolve to the server. - `SITE_ADDRESS` must already resolve to the server.
- `ACME_EMAIL` is required and must be a valid contact address. - `ACME_EMAIL` is required and must be a valid contact address.
- Ports **80 and 443** must be reachable from the internet. Port 80 is not optional — - Ports **80 and 443** must be reachable from the internet. Port 80 is mandatory:
it is what the ACME challenge uses to prove domain ownership. the ACME challenge uses it to prove domain ownership.
Certificates are stored in the bind-mounted `./caddy/data` directory. Keep it: without it Caddy re-issues Certificates are stored in the bind-mounted `./caddy/data` directory. Keep it:
on every recreate and will hit the Let's Encrypt rate limit after a handful of restarts. without it, Caddy requests a new certificate after every recreation and will hit
Let's Encrypt rate limits after only a handful of restarts.
To update, pull and rebuild: To update, pull and rebuild:
```sh git pull && docker compose up -d --build
git pull && docker compose up -d --build
```
Running clients do not reload on their own. Each app checks hourly for a new build and Running clients do not reload automatically. Each app checks hourly for a new build
offers an **"Installer"** banner — but only while no working day is in progress, so an and offers an **Install (Installer)** banner, but only when no workday is in progress,
update can never interrupt someone mid-day. so an update cannot interrupt a workday.
### HTTPS is not optional ### HTTPS is required
Browsers only register a service worker in a secure context. Served over plain `http://` Browsers only register a service worker in a secure context. Served over plain
to an IP or hostname, the app still loads but has **no offline support, cannot be `http://` at an IP address or hostname, the app still loads but has **no offline
installed, and never updates** — which removes the point of the design. `localhost` is support, cannot be installed, and never updates**, defeating the design. `localhost`
the only exception, which is why `npm run dev` works without certificates. is the only exception, which is why `npm run dev` works without certificates.
### Behind an existing reverse proxy ### Behind an existing reverse proxy
The current Caddy configuration terminates TLS itself and accepts PROXY protocol The current Caddy configuration terminates TLS itself and accepts the PROXY protocol
from the address or CIDR in `TRUST_PROXY`. Set this value to the real upstream from the address or CIDR in `TRUST_PROXY`. Set this to the actual upstream proxy
proxy address; `192.0.2.10/32` in `.env.example` is a documentation placeholder. address; `192.0.2.10/32` in `.env.example` is a documentation placeholder.
`LISTEN_IP` selects the local interface for the published ports and defaults to `LISTEN_IP` selects the local interface for published ports and defaults to
`0.0.0.0`. A deployment that terminates TLS elsewhere needs its own Caddy and `0.0.0.0`. A deployment that terminates TLS elsewhere needs its own Caddy and port
port configuration; changing `SITE_ADDRESS` alone does not configure that mode. configuration; changing `SITE_ADDRESS` alone does not configure that mode.
The app must be served from the root of its domain. `start_url` and the service worker The app must be served from the root of its domain. `start_url` and the service
scope are both `/`, so hosting it under a subpath will not work without changing the worker scope are both `/`, so hosting it under a subpath will not work without
manifest and Vite's `base`. changing the manifest and Vite's `base`.
## Architecture ## Architecture
Two decisions shape everything else. Two decisions shape everything else.
**Raw timestamps are stored; rounding is applied only at render.** Changing the rounding **Raw timestamps are stored; rounding is applied only when rendering.** Changing
setting therefore corrects past days too. Adjacent entries share one stored instant — the rounding setting therefore corrects past days too. Adjacent entries share one
entry *N*'s end *is* entry *N+1*'s start — so rounding can never open a gap between stored instant - entry *N*'s end is entry *N+1*'s start - so rounding can never open
them. Time pickers follow the selected rounding grid; opening an unchanged time a gap between them. Time pickers follow the selected rounding grid; opening an
preserves its stored timestamp. Summaries apply the same rounding at render. unchanged time preserves its stored timestamp. Summaries apply the same rounding
when rendered.
**Suggestions are derived from history, not from counter tables.** There is nothing to **Suggestions are derived from history, not counter tables.** Nothing needs to be
keep in sync; the model is rebuilt in memory from the entries. The tradeoff is that a kept in sync; the model is rebuilt in memory from entries. The tradeoff is that a
short retention window degrades predictions, which is why retention defaults to two short retention window degrades predictions, which is why retention defaults to
years. two years.
``` src/lib/
src/lib/ time.js local dates, rounding, formatting (pure)
time.js local dates, rounding, formatting (pure) predict.js suggestion engine (pure)
predict.js the suggestion engine (pure) resume.js logbook text builder (pure)
resume.js logbook text builder (pure) db.js Dexie schema and chain invariant
db.js Dexie schema and the chain invariant day.svelte.js day lifecycle state machine
day.svelte.js day lifecycle state machine
```
Pure helpers and database/lifecycle operations have regression tests. `db.js` Pure helpers and database/lifecycle operations have regression tests. `db.js` keeps
keeps day and entry changes in shared transactions, including creation, closure, day and entry changes in shared transactions, including creation, closure, resuming,
reopening and boundary corrections. and boundary corrections.
## Pauses et nouveaux parcours ## Breaks and newer workflows
**Journée → Commencer une pause** ferme le poste courant. **Reprendre le travail** **Workday → Start a break (Journée → Commencer une pause)** closes the current entry.
propose la dernière tâche de travail et son client, immédiatement modifiables. **Resume work (Reprendre le travail)** suggests the last work task and its client,
Une pause a ses propres horaires et son libellé, sans client ni type de repas. which can be edited immediately. A break has its own times and label, without a
Le détail montre tous les postes ; la consultation reste séparée de la correction. client or meal type. Details show every entry; viewing is separate from editing.
Le récapitulatif et sa copie excluent les pauses. Le transport entre dans le total Summaries and copied text exclude breaks. Travel is included in total hours present;
des heures de présence, les repas et pauses en sont exclus. Seules les statistiques montrent le meals and breaks are excluded. Statistics show total break time separately from meals.
cumul des pauses, séparément des repas. Les intervalles vides importés sont préservés. Imported gaps are preserved.
**Réglages → Gérer les automatismes** et **Gérer les jours non travaillés** ouvrent **Settings (Réglages) → Manage automations (Gérer les automatismes)** and
des pages dédiées ; le bouton Retour du navigateur fonctionne via les routes hash. **Manage non-working days (Gérer les jours non travaillés)** open dedicated pages;
**Gérer les données et sauvegardes** ouvre aussi une page dédiée, avec retour aux the browser Back button works through hash routes. **Manage data and backups (Gérer
réglages, conservation de la durée après rechargement et accès hors ligne. les données et sauvegardes)** also opens a dedicated page, with a return to settings,
Les réglages sont écrits par transaction sur la dernière valeur persistée pour retention persistence after reload, and offline access.
préserver les modifications d’autres onglets. Les erreurs de démarrage et
actualisation sont affichées avec une action Réessayer.
La checklist avant production et les validations restant à exécuter sont suivies Settings are written transactionally against the latest persisted value to preserve
dans [PRODUCTION.md](PRODUCTION.md). changes made in other tabs. Startup and refresh errors are shown with a Retry action.
## Sauvegarde vérifiée et validation navigateur The production checklist and remaining validation steps are tracked in
[PRODUCTION.md](PRODUCTION.md).
**Exporter les données** prépare le fichier et enregistre son empreinte complète. ## Verified backups and browser validation
**Vérifier le fichier exporté…** relit le fichier téléchargé, contrôle qu’il est
importable et compare son contenu à l’export. La date de la dernière sauvegarde
vérifiée est distincte de celle du dernier export préparé. Après sept jours sans
export vérifié, Pardis affiche un rappel pendant son utilisation. L’export reste local ; conservez le fichier hors de cet appareil. La synchronisation
facultative utilise un coffre distinct et ne remplace pas cette sauvegarde.
La purge affiche une liste figée de journées. Sauvegarde et suppression partagent **Export data (Exporter les données)** prepares a file and saves its full fingerprint.
une transaction ; une correction concurrente bloque la purge et demande une **Verify exported file… (Vérifier le fichier exporté…)** reads the downloaded file,
nouvelle consultation. Les autres journées récemment importées restent intactes. checks that it can be imported, and compares its contents with the prepared export.
The date of the last verified backup is separate from the most recent prepared export.
After seven days without a verified export, Pardis shows a reminder while in use.
Export remains local; keep the file outside this device. Optional synchronization
uses a separate vault and does not replace backups.
Les tests Playwright utilisent des contextes neufs et des données synthétiques, Deletion displays a fixed list of days. Backup and deletion share a transaction; a
à 320 px. Ils vérifient consultation/correction, focus, Retour, reprise à deux concurrent correction blocks deletion and requires reviewing the list again. Other
onglets, export vérifié, purge/restauration et rechargement hors ligne. Ils couvrent recently imported days remain untouched.
aussi le nouvel écran Données, le retour du champ de conservation à sa valeur
persistée après un échec, le refus d’un export modifié et l’annulation complète
d’une purge en erreur, avec préservation de la sauvegarde précédente. Traces,
vidéos et captures sont désactivées. Pour un Chrome déjà installé, définir
`PARDIS_CHROME_PATH` avec son chemin ; sinon installer Chromium avec Playwright.
La CI installe Chromium Headless Shell et ses dépendances avant de lancer ces
tests. Elle télécharge uniquement la variante utilisée par les tests sans interface
et accorde 180 secondes à chaque connexion de téléchargement, avec les nouvelles
tentatives intégrées à Playwright. L’installation dispose de 20 minutes et le job
de validation de 30 minutes. Une erreur d’installation bloque le déploiement.
Playwright tests use fresh contexts and synthetic data at 320 px. They cover
viewing/editing, focus, Back navigation, resuming in two tabs, verified export,
deletion/recovery, and offline reload. They also cover the dedicated Data screen,
restoring a retention field after a save failure, rejecting a modified export, and
fully rolling back a failed deletion while preserving the previous backup. Traces,
videos, and screenshots are disabled. To use an installed Chrome, set
`PARDIS_CHROME_PATH` to its path; otherwise install Chromium with Playwright.
CI installs Chromium Headless Shell and its dependencies before running the tests.
It downloads only the headless browser build used by the tests and gives each
download attempt 180 seconds, with Playwright's built-in retries. Browser setup has
a 20-minute limit and the validation job has a 30-minute limit. An installation
failure blocks deployment.
## Synchronisation facultative entre appareils ## Optional device synchronization
**Données et sauvegardes → Synchronisation et appareils** permet de créer un coffre **Data and backups (Données et sauvegardes) → Synchronization and devices
chiffré sans e-mail ni mot de passe, puis de lier des appareils avec un code temporaire (Synchronisation et appareils)** creates an encrypted vault without an email or
ou un QR code et une confirmation sur l’écran dédié. La saisie reste locale et password, then links devices using a temporary code or QR code and confirmation
fonctionne hors ligne. Les journées concurrentes sont conservées pour résolution. on the dedicated screen. Data entry stays local and works offline. Concurrent days
Un document de secours permet de retrouver l’accès après perte des appareils. are retained for conflict resolution. A recovery document restores access after
losing the devices.
Les règles métier sont partagées ; le thème et le suivi des sauvegardes restent Business rules are shared; the theme and backup tracking stay local. One device is
locaux. Un appareil est responsable des automatismes, avec proposition de transfert responsible for automations, with a transfer prompt when opening the app on a
à l’ouverture sur un appareil secondaire. Les demandes d’association ne produisent secondary device. Pairing requests do not create alerts elsewhere in the app.
aucune alerte ailleurs dans l’application.
Le protocole, les limites de sécurité, le fonctionnement des suppressions et les The protocol, security limits, deletion behavior, and server backup/restore commands
commandes de sauvegarde/restauration du serveur sont documentés dans [SYNC.md](SYNC.md). are documented in [SYNC.md](SYNC.md). For development, also run `npm run sync:server`
Pour le développement, lancer aussi `npm run sync:server` avec Node.js 24. with Node.js 24.
## License and font credits
Pardis is distributed under the GNU General Public License, version 3 only
(GPL-3.0-only). See [LICENSE.md](LICENSE.md) for the full license text. The bundled font
files are separate works and remain under the SIL Open Font License, version 1.1;
their license and copyright notices are included in [public/fonts/OFL.txt](public/fonts/OFL.txt).
Copyright (C) 2026 EI Adrien Weiskopff.
- **Bebas Neue**, by Ryoichi Tsunekawa - SIL Open Font License 1.1.
- **Charis SIL** - SIL Open Font License 1.1. See the [official Charis SIL site](https://software.sil.org/fr/charis/).

BIN
public/fonts/BebasNeue.otf Normal file

Binary file not shown.

Binary file not shown.

97
public/fonts/OFL.txt Normal file
View File

@@ -0,0 +1,97 @@
Charis SIL
Copyright (c) 1997-2014, SIL International (http://www.sil.org/)
with Reserved Font Names "Charis" and "SIL".
Bebas Neue
Copyright (c) 2010 by Ryoichi Tsunekawa. All rights reserved.
The SIL Open Font License below applies to both fonts.
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to provide
a free and open framework in which fonts may be shared and improved in
partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components
as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software
to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer
or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a
copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or in
the appropriate machine-readable metadata fields within text or binary
files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole, must
be distributed entirely under this license, and must not be distributed
under any other license. The requirement for fonts to remain under this
license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -203,7 +203,7 @@
border: 0; border: 0;
border-radius: 0; border-radius: 0;
min-height: 68px; min-height: 68px;
font-size: 0.95rem; font-size: 1.35rem;
} }
nav button + button { nav button + button {
@@ -215,5 +215,5 @@
color: var(--accent-fg); color: var(--accent-fg);
} }
nav.with-stats { grid-template-columns: repeat(4, 1fr); } nav.with-stats { grid-template-columns: repeat(4, 1fr); }
nav.with-stats button { padding: 0 4px; font-size: 0.75rem; min-width: 0; } nav.with-stats button { padding: 0 4px; min-width: 0; }
</style> </style>

View File

@@ -8,26 +8,21 @@
* - Nothing tappable is under 64px; primary actions are 88px. * - Nothing tappable is under 64px; primary actions are 88px.
*/ */
/* @font-face {
* When the font files are ready, add their @font-face declarations here and font-family: 'Pardis Text';
* place them under public/fonts/ (for example, /fonts/pardis-text.woff2). src: url('/fonts/CharisSIL-R.ttf') format('truetype');
* Keeping these declarations commented avoids requests for files not yet shipped. font-style: normal;
* font-weight: 400;
* @font-face { font-display: swap;
* font-family: 'Pardis Text'; }
* src: url('/fonts/pardis-text.woff2') format('woff2');
* font-style: normal; @font-face {
* font-weight: 400; font-family: 'Pardis Headings';
* font-display: swap; src: url('/fonts/BebasNeue.otf') format('opentype');
* } font-style: normal;
* @font-face { font-weight: 400;
* font-family: 'Pardis Headings'; font-display: swap;
* src: url('/fonts/pardis-headings.woff2') format('woff2'); }
* font-style: normal;
* font-weight: 400;
* font-display: swap;
* }
*/
:root { :root {
--bg: #ffffff; --bg: #ffffff;
@@ -113,8 +108,8 @@ h2,
h3 { h3 {
font-family: var(--font-heading); font-family: var(--font-heading);
margin: 0; margin: 0;
font-weight: 800; font-weight: 400;
letter-spacing: -0.01em; letter-spacing: 0.0em;
} }
h1 { h1 {
font-size: 1.6rem; font-size: 1.6rem;
@@ -128,6 +123,9 @@ h3 {
p { p {
margin: 0; margin: 0;
} }
a, a:visited {
color: var(--accent);
}
/* Times and durations must never reflow as digits change. */ /* Times and durations must never reflow as digits change. */
.tnum { .tnum {
@@ -145,8 +143,9 @@ p {
/* ------------------------------------------------------------------ controls */ /* ------------------------------------------------------------------ controls */
button { button {
font: inherit; font-family: var(--font-heading);
font-weight: 700; font-weight: 400;
font-size: 1.2rem;
color: var(--fg); color: var(--fg);
background: var(--bg); background: var(--bg);
border: var(--border) solid var(--line); border: var(--border) solid var(--line);

View File

@@ -10,6 +10,8 @@
enregistre les journées, les tâches, les clients, les repas et les pauses, enregistre les journées, les tâches, les clients, les repas et les pauses,
puis prépare un récapitulatif à consulter ou à copier. puis prépare un récapitulatif à consulter ou à copier.
</p> </p>
<p>L'application et le service de synchronisation sont fournis gratuitement et
sans aucune garantie de disponibilité ou de continuité.</p>
<section class="card stack"> <section class="card stack">
<h2>Fonctionnement</h2> <h2>Fonctionnement</h2>
@@ -49,30 +51,43 @@
publiques des appareils, liens entre révisions, tailles et dates de publiques des appareils, liens entre révisions, tailles et dates de
connexion. Ces données sont conservées un an, conformément à la loi. connexion. Ces données sont conservées un an, conformément à la loi.
</p> </p>
<p>
Supprimer une journée de l’historique synchronisé ne supprime pas ses
anciennes révisions chiffrées du coffre. Elles y restent jusqu’à la
suppression du coffre. Les sauvegardes de l’hébergement peuvent aussi
conserver des données supprimées jusqu’à l’expiration de leur rétention.
</p>
</section> </section>
<section class="card stack"> <section class="card stack">
<h2>Opérateur de cette instance</h2> <h2>Opérateur de cette instance</h2>
<p> <p>
Le service de synchronisation de cette instance est hébergé sur un serveur L'application est développée par EI Adrien Weiskopff - clawd.fr.
sauvegardé quotidiennement. Les sauvegardes de ce serveur
protègent le service en ligne ; elles ne sauvegardent pas les données
conservées uniquement dans le navigateur de chaque utilisateur.
</p>
<p>
L'application est développée par Adrien Weiskopff.
L'application et son serveur de production sont hébergés en France par L'application et son serveur de production sont hébergés en France par
clawd.fr (EI Adrien Weiskopff). EI Adrien Weiskopff - clawd.fr.
Pour plus d'informations rendez-vous sur le site <a href="https://clawd.fr" target="_blank" title="vers le site de l'hébergeur">https://clawd.fr</a> ou écrivez Pour plus d'informations rendez-vous sur le site <a href="https://clawd.fr" target="_blank" title="vers le site de l'hébergeur">https://clawd.fr</a>
à l'adresse <a href="mailto:pardis@clawd.fr" title="contacter le développeur">pardis@clawd.fr</a>. ou écrivez à l'adresse <a href="mailto:pardis@clawd.fr" title="contacter le développeur">pardis@clawd.fr</a>.
</p> </p>
</section> </section>
<section class="card stack">
<h2>Licence et crédits typographiques</h2>
<p>
Le code source de Pardis est distribué sous la licence GNU General Public
License, version 3 uniquement (GPL-3.0-only). Consultez le
<a href="https://www.gnu.org/licenses/gpl-3.0.html" target="_blank" rel="noopener noreferrer">texte de la GPL v3</a>.
Les fichiers de polices sont des œuvres distinctes du code de l’application
et restent sous leur propre licence, la SIL Open Font License, version 1.1.
Le texte de l’OFL et les mentions de droits d’auteur accompagnent les fichiers
dans la distribution du projet.
</p>
<p>Copyright (C) 2026 EI Adrien Weiskopff.</p>
<ul>
<li>Bebas Neue, par Ryoichi Tsunekawa — SIL Open Font License 1.1.</li>
<li>
Charis SIL — SIL Open Font License 1.1. Voir le
<a href="https://software.sil.org/fr/charis/" target="_blank" rel="noopener noreferrer">site officiel de Charis SIL</a>.
</li>
</ul>
<p>
<a href="https://openfontlicense.org/open-font-license-official-text/" target="_blank" rel="noopener noreferrer">Texte officiel de la SIL Open Font License 1.1</a>.
</p>
</section>
</main> </main>
<style> <style>

View File

@@ -42,7 +42,7 @@ export default defineConfig({
// precache entries for the same URL with different revisions. Workbox // precache entries for the same URL with different revisions. Workbox
// rejects conflicting entries, the install handler throws, and nothing // rejects conflicting entries, the install handler throws, and nothing
// gets cached at all — the app silently stops working offline. // gets cached at all — the app silently stops working offline.
globPatterns: ['**/*.{js,css,html,svg,png,ico}'], globPatterns: ['**/*.{js,css,html,svg,png,ico,ttf,otf,txt}'],
cleanupOutdatedCaches: true, cleanupOutdatedCaches: true,
clientsClaim: false, clientsClaim: false,
}, },